如何检查PHP数组是关联数组还是顺序数组?


780

PHP将所有数组视为关联数组,因此没有任何内置函数。谁能推荐一种相当有效的方法来检查数组是否仅包含数字键?

基本上,我希望能够区分以下两者:

$sequentialArray = array('apple', 'orange', 'tomato', 'carrot');

和这个:

$assocArray = array('fruit1' => 'apple', 
                    'fruit2' => 'orange', 
                    'veg1' => 'tomato', 
                    'veg2' => 'carrot');

382
您的代码中有一个错误:番茄是一种水果。
OlleHärstedt'3

9
这种方法有一些警告,但我经常这样做if (isset($array[0])),这既简单又快速。当然,您首先应该确保数组不为空,并且应该对数组的可能内容有所了解,以使该方法不会失败(例如混合数字/关联或非顺序)。
Gras Double

@OlleHärstedt不是根据美国高等法院。;-)
MC Emperor

Answers:


621

您问了两个不完全相同的问题:

  • 首先,如何确定数组是否只有数字键
  • 其次,如何确定数组是否具有从0开始的连续数字键

考虑您实际上需要哪种行为。(这也许可以满足您的目的。)

kurO船长很好地回答了第一个问题(只需检查所有键是否都是数字)。

对于第二个问题(检查数组是否为零索引和顺序的),可以使用以下函数:

function isAssoc(array $arr)
{
    if (array() === $arr) return false;
    return array_keys($arr) !== range(0, count($arr) - 1);
}

var_dump(isAssoc(['a', 'b', 'c'])); // false
var_dump(isAssoc(["0" => 'a', "1" => 'b', "2" => 'c'])); // false
var_dump(isAssoc(["1" => 'a', "0" => 'b', "2" => 'c'])); // true
var_dump(isAssoc(["a" => 'a', "b" => 'b', "c" => 'c'])); // true

31
非常优雅的解决方案。请注意,在(模糊)空数组的情况下,它返回TRUE。
乔纳森·利德贝克

29
我认为将顺序数组视为关联数组的特殊情况更为有用。因此,每个数组都是关联的,但是只有一些是顺序的。因此,功能isSequential()比更具意义isAssoc()。在这种函数中,空数组视为顺序的。该公式可以array() === $arr || !isAssoc($arr)
donquixote

18
我认为,如果在提取所有键之前先检查isset($ arr [0])是否为false,这将避免很多潜在的cpu时间和内存,因为如果数组不为空但在0中没有元素,则可以明显关联位置。由于“最”真实的关联数组具有字符串作为键,因此对于此类函数的一般情况,这应该是一个不错的优化。
OderWat

10
@OderWat-应该使用优化,array_key_exists而不是使用,isset因为如果零元素为空值,则isset将错误地返回false。在这样的数组中,空值通常应该是合法值。
OCDev

@MAChitgarha,您的编辑更改了该函数的行为,而没有任何原因的解释,并使它与上面散文中实际应做的描述相矛盾。我已经还原了。
Mark Amery

431

仅检查数组是否具有非整数键(而不是数组是顺序索引还是零索引):

function has_string_keys(array $array) {
  return count(array_filter(array_keys($array), 'is_string')) > 0;
}

如果至少有一个字符串键,$array将被视为一个关联数组。


22
这种方法比看起来好得多。如果count(filtered_array)== count(original_array),则它是一个assoc数组。如果count(filtered_array)== 0,则它是一个索引数组。如果count(filtered_array)<count(original_array),则数组同时具有数字键和字符串键。
Jamol 2012年

5
@MikePretzlaw 当然会迭代;(显然)没有查看数组中的所有键,就不可能确定数组的所有键是否为整数。我假设下面我们应该看到的非迭代替代方案是$isIndexed = array_values($arr) === $arr;?我要问的是:您认为如何array_values()运作?您认为如何===应用于数组?答案当然是它们还会遍历数组。
Mark Amery 2015年

4
@ARW “ PHP似乎可以将所有内容强制转换为数组定义中的int。” -是的,就是这样。最大的WTF是它甚至会浮动。如果尝试var_dump([1.2 => 'foo', 1.5 => 'bar']);,就会发现得到数组[1 => 'bar']。根本找不到钥匙的原始类型。是的,这一切都很糟糕;PHP的数组到目前为止是该语言中最糟糕的部分,大部分损坏是无法弥补的,这归因于对传统数组使用单一构造的想法,而从一开始就对传统哈希图使用可怕的想法。
Mark Amery 2015年

30
@MarkAmery上面的内容虽然很简单,但是可以保证100%遍历数组。这样做会更有效率,尤其是在处理大型数组时,如果要检查字符串或整数并在发现的第一个字符串中出现就可以了。例如: function isAssociative($arr) { foreach ($arr as $key => $value) { if (is_string($key)) return true; } return false; }
以为

1
@Thought您的代码工作非常快,但无法检测到顺序数组。示例array(1 => 'a', 0 => 'b', 2 => 'c')将变为false(顺序数组),而应变为true(关联数组)。toolsqa.com/data-structures/array-in-programming 我不确定密钥必须是升序吗?(0,1,...)
vee

132

当然,这是一个更好的选择。

<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;

52
这将复制数组中的值,这可能非常昂贵。您最好检查一下数组键。
meagar

8
我只是用==; 我认为这里不需要===。但是要回答“未设置且不起作用”的问题:取消设置第一个元素后,它不再是从0开始的整数索引数组。因此IMO确实起作用。
Grantwparks 2012年

4
同意@grantwparks:稀疏数组未编制索引。有趣的是,因为实际上无法从索引数组的中间删除一个元素,PHP基本上是将所有数组声明为关联数组,而数值只是“为我补上键”的版本。
RickMeasham

7
我唯一的问题===是,即使我们只对键感兴趣,如果值相等也将浪费时间。因此,我更喜欢该$k = array_keys( $arr ); return $k === array_keys( $k );版本。
杰西

5
补充说明一下,这在用数字键指定的数组出现故障时失败。例如$ myArr = array(0 =>'a',3 =>'b',4 => 1,2 => 2,1 =>'3'); 一种可能的解决方法是在进行测试之前运行ksort($ arr)
Scott

77

这个问题的许多评论者不了解数组在PHP中的工作方式。从数组文档中

键可以是整数或字符串。如果键是整数的标准表示,它将被解释为整数(即​​“ 8”将被解释为8,而“ 08”将被解释为“ 08”)。键中的浮点数将被截断为整数。索引数组和关联数组类型与PHP中的类型相同,都可以包含整数索引和字符串索引。

换句话说,没有像“ 8”这样的数组键,因为它将始终(无声地)转换为整数8。因此,不必在整数和数字字符串之间进行区分。

如果您想以最有效的方式检查数组中的非整数键,而又不复制数组的一部分(例如array_keys()进行复制)或全部复制(例如foreach进行复制),请执行以下操作:

function keyedNext( &$arr, &$k){
    $k = key($arr);
    return next($arr);
}

for ($k = key(reset($my_array)); is_int($k); keyedNext($my_array,$k))
    $onlyIntKeys = is_null($k);

之所以有效,是因为当当前数组位置无效时,key()返回NULL,并且NULL永远不可能是有效的键(如果尝试使用NULL作为数组键,则它会静默转换为“”)。


这对于非顺序整数键不起作用。尝试使用[2 =>'a',4 =>'b']。
DavidJ 2012年

2
@DavidJ,“不工作”是什么意思?它成功地确定所有键都是整数。您是否声称像您发布的数组那样的数组不应该被视为“数值数组”?
coredumperror13年

7
非关联数组必须具有按此严格顺序排列的键,范围从0count($array)-1。进行初步检查is_array()可能会有所帮助。添加一个递增变量以检查关键顺序:for ($k = 0, reset($array) ; $k === key($array) ; next($array)) ++$k;达成交易。
ofavre 2013年

2
使用foreach而不是显式迭代大约快一倍。
ofavre 2013年

1
如果您想使其成为一个函数: function isAssocStr($array) { for (reset($array); is_int(key($array)); next($array)) { if (is_null(key($array))) return false; } return true; }
GreeKatrina

39

如《任择议定书》所述

PHP将所有数组视为关联数组

编写一个检查数组是否有关联的函数不是很明智(IMHO)。首先,第一件事PHP数组中的键是什么?:

可以是一个整数字符串

这意味着有3种可能的情况:

  • 情况1.所有键都是数字 / 整数
  • 情况2。所有键都是字符串
  • 情况3.一些键是字符串,一些键是数字 / 整数

我们可以使用以下功能检查每种情况。

情况1:所有键都是数字 / 整数

注意对于空数组,此函数也返回true

//! Check whether the input is an array whose keys are all integers.
/*!
    \param[in] $InputArray          (array) Input array.
    \return                         (bool) \b true iff the input is an array whose keys are all integers.
*/
function IsArrayAllKeyInt($InputArray)
{
    if(!is_array($InputArray))
    {
        return false;
    }

    if(count($InputArray) <= 0)
    {
        return true;
    }

    return array_unique(array_map("is_int", array_keys($InputArray))) === array(true);
}

情况2:所有键都是字符串

注意对于空数组,此函数也返回true

//! Check whether the input is an array whose keys are all strings.
/*!
    \param[in] $InputArray          (array) Input array.
    \return                         (bool) \b true iff the input is an array whose keys are all strings.
*/
function IsArrayAllKeyString($InputArray)
{
    if(!is_array($InputArray))
    {
        return false;
    }

    if(count($InputArray) <= 0)
    {
        return true;
    }

    return array_unique(array_map("is_string", array_keys($InputArray))) === array(true);
}

情况3.一些键是字符串,一些键是数字 / 整数

注意对于空数组,此函数也返回true

//! Check whether the input is an array with at least one key being an integer and at least one key being a string.
/*!
    \param[in] $InputArray          (array) Input array.
    \return                         (bool) \b true iff the input is an array with at least one key being an integer and at least one key being a string.
*/
function IsArraySomeKeyIntAndSomeKeyString($InputArray)
{
    if(!is_array($InputArray))
    {
        return false;
    }

    if(count($InputArray) <= 0)
    {
        return true;
    }

    return count(array_unique(array_map("is_string", array_keys($InputArray)))) >= 2;
}

它遵循:


现在,让一个数组成为我们都已习惯的“真正”数组,这意味着:

  • 它的键全是数字 / 整数
  • 它的密钥是顺序的(即,按步骤1增加)。
  • 其键从零开始

我们可以使用以下功能进行检查。

情况3a。键是数字 / 整数顺序从零开始的键

注意对于空数组,此函数也返回true

//! Check whether the input is an array whose keys are numeric, sequential, and zero-based.
/*!
    \param[in] $InputArray          (array) Input array.
    \return                         (bool) \b true iff the input is an array whose keys are numeric, sequential, and zero-based.
*/
function IsArrayKeyNumericSequentialZeroBased($InputArray)
{
    if(!is_array($InputArray))
    {
        return false;
    }

    if(count($InputArray) <= 0)
    {
        return true;
    }

    return array_keys($InputArray) === range(0, count($InputArray) - 1);
}

注意事项/陷阱(或者,甚至更多有关PHP中数组键的特殊事实)

整数键

这些数组的键是整数

array(0 => "b");
array(13 => "b");
array(-13 => "b");          // Negative integers are also integers.
array(0x1A => "b");         // Hexadecimal notation.

字符串键

这些数组的键是字符串

array("fish and chips" => "b");
array("" => "b");                                   // An empty string is also a string.
array("stackoverflow_email@example.com" => "b");    // Strings may contain non-alphanumeric characters.
array("stack\t\"over\"\r\nflow's cool" => "b");     // Strings may contain special characters.
array('$tα€k↔øv∈rflöw⛄' => "b");                    // Strings may contain all kinds of symbols.
array("functіon" => "b");                           // You think this looks fine? Think again! (see https://stackoverflow.com/q/9246051/1402846)
array("ま말轉转ДŁ" => "b");                         // How about Japanese/Korean/Chinese/Russian/Polish?
array("fi\x0sh" => "b");                            // Strings may contain null characters.
array(file_get_contents("https://www.google.com/images/nav_logo114.png") => "b");   // Strings may even be binary!

看起来像字符串的整数键

如果您认为键入的内容array("13" => "b")字符串,那是错误的。从这里的文档:

包含有效整数的字符串将转换为整数类型。例如,键“ 8”实际上将存储在8以下。另一方面,“ 08”将不是强制转换,因为它不是有效的十进制整数。

例如,这些数组的键是整数

array("13" => "b");
array("-13" => "b");                        // Negative, ok.

但是这些数组的关键是字符串

array("13." => "b");
array("+13" => "b");                        // Positive, not ok.
array("-013" => "b");
array("0x1A" => "b");                       // Not converted to integers even though it's a valid hexadecimal number.
array("013" => "b");                        // Not converted to integers even though it's a valid octal number.
array("18446744073709551616" => "b");       // Not converted to integers as it can't fit into a 64-bit integer.

而且,根据文档

整数的大小取决于平台,尽管通常的最大值约为20亿(32位带符号)。64位平台的最大值通常约为9E18(Windows始终为32位)。PHP不支持无符号整数。

因此,此数组的键可以是整数,也可以不是整数 -这取决于您的平台。

array("60000000000" => "b");                // Array key could be integer or string, it can fit into a 64-bit (but not 32-bit) integer.

更糟的是,PHP趋向于如果整数是2的附近31 = 2,147,483,648边界(参见错误51430错误52899)。例如,在(在Windows 7 PHP 5.3.8上XAMPP 1.7.7)我当地的环境,var_dump(array("2147483647" => "b"))

array(1) {
    [2147483647]=>
    string(1) "b"
}   

但在此使用键盘的现场演示(PHP 5.2.5)上,相同的表达式给出了

array(1) {
    ["2147483647"]=>
    string(1) "b"
}

因此,密钥是一个环境中的整数,而在另一个环境中则是字符串,即使它2147483647是有效的带符号的32位整数


2
除了下面要提到的,它涉及到为被检查的对象创建一个重复的数组,这对于大型数组而言非常昂贵,并且有可能导致共享主机内存不足而崩溃。
播客

35

速度方面:

function isAssoc($array)
{
    return ($array !== array_values($array));
}

内存方面:

function isAssoc($array)
{
    $array = array_keys($array); return ($array !== array_keys($array));
}

以下数组:array(02 => 11,1,2,456); 被示出为使用上述算法不具有数字键,即使02 === 2
Galileo_Galilei

20
function checkAssoc($array){
    return  ctype_digit( implode('', array_keys($array) ) );
}

2
这是唯一可以解决以下问题的答案(在我发表评论时):$ array = array(0 =>'blah',2 =>'yep',3 =>'wahey')
Shabbyrobe

array('1'=>'asdf', '2'=>'too')实际上不会被视为关联数组(键实际上是字符串)
Captain kurO 2011年

1
@CaptainkurO您的意思是数字。它是一个关联数组。
devios1 2012年

1
true如果键为:零,整数(仅正),空字符串或上述任意组合(例如字符串“ 09”),则此函数返回。此功能不考虑按键的顺序。因此array(0=>'blah', 2=>'yep', 3=>'wahey')array(0=>'blah', 2=>'yep', 1=>'wahey')并且array('blah', 'yep', 'wahey')根据此功能都是关联的,而array('a'=>'blah', 'b'=>'yep', 'c'=>'wahey')不是。

@CaptainkurO您不正确。“ 1”和“ 2”将存储为整数。从2011年5月11日19:34阅读松鼠答案的引用部分。PHP不存储看上去完全像整数的字符串键。它将那些转换为整数。
Buttle Butkus

20

因此,实际上,最有效的方法是:

function is_assoc($array){
   $keys = array_keys($array);
   return $keys !== array_keys($keys);
}

之所以起作用,是因为它将键(对于连续数组,其始终为0,1,2等)与键(其始终为0,1,2等)的键进行比较。


1
聪明,但不好。为什么这是“最有效的”?仅将array_keys($ a)与range(0,count($ a))进行比较会更具可读性。根据我的经验,最聪明的解决方案很少是最好的解决方案。尤其是当聪明时,比起显而易见的干净选择实际上没有任何价值。
Shane H

4
这个函数返回truearray(1=>"a"),但falsearray("a"=>"a")。如果!=替换为,将会更有意义!==

1
@庞,你是对的。我以为您的评论起初肯定一定是错误的,但是令我惊讶的是,[0] == ['a']在PHP中(因为0 == 'a',实际上是0 == 'banana')。PHP的==运算符很疯狂。
Mark Amery

2
就涉及调用array_keys而不是仅检查直到找到非顺序整数索引而言,它不是高效的。无论如何,您都在执行此操作,但是您已经复制了一个大数组。
播客

17

我已经使用了array_keys($obj) !== range(0, count($obj) - 1)array_values($arr) !== $arr(两者是彼此的对偶,尽管第二个比第一个便宜),但是对于非常大的数组都失败了。

这是因为array_keysarray_values都是非常昂贵的操作(因为它们构建了一个新的数组,其大小大约是原始数组的大小)。

与上面提供的方法相比,以下功能更强大:

function array_type( $obj ){
    $last_key = -1;
    $type = 'index';
    foreach( $obj as $key => $val ){
        if( !is_int( $key ) || $key < 0 ){
            return 'assoc';
        }
        if( $key !== $last_key + 1 ){
            $type = 'sparse';
        }
        $last_key = $key;
    }
    return $type;
}

还要注意,如果您不希望将稀疏数组与关联数组区分开,则可以简单地'assoc'从两个if块中返回。

最后,尽管此页面上的“优雅”看起来比许多“解决方案”要少得多,但实际上它的效率要高得多。几乎所有关联数组都将立即被检测到。仅索引数组将得到详尽检查,并且上面概述的方法不仅详尽检查索引数组,而且还会复制它们。


13

我认为以下两个函数是检查“数组是关联数组还是数值数组”的最佳方法。由于“数字”可能只表示数字键或仅表示顺序数字键,因此下面列出了两个函数来检查任一条件:

function is_indexed_array(&$arr) {
  for (reset($arr); is_int(key($arr)); next($arr));
  return is_null(key($arr));
}

function is_sequential_array(&$arr, $base = 0) {
  for (reset($arr), $base = (int) $base; key($arr) === $base++; next($arr));
  return is_null(key($arr));
}

第一个功能检查每个键是否为整数值。第二个函数检查每个键是否为整数值,此外还检查所有键是否都是从$ base开始的顺序键,该值默认为0,因此如果不需要指定其他基值,可以将其省略。如果将读取指针移到数组末尾,则key($ my_array)返回null,这是for循环的结尾,如果所有键都是整数,则for循环之后的语句返回true。如果不是,则循环会过早结束,因为键的类型为字符串,并且for循环后的语句将返回false。另外,后一个函数在每次比较后向$ base添加一个,以便能够检查下一个键是否具有正确的值。严格比较使它还可以检查密钥是否为整数类型。当省略$ base或确保仅使用整数调用$ base时,可以忽略for循环第一部分中的$ base =(int)$ base部分。但是由于不确定每个人,因此我将其保留。无论如何,该语句仅执行一次。我认为这些是最有效的解决方案:

  • 明智的记忆:不复制数据或键范围。进行一次array_values或array_keys的过程似乎更短(代码更少),但是请记住,一旦调用该函数,后台就会发生什么。是的,比其他一些解决方案中的(可见)语句更多,但这不是很重要,不是吗?
  • 时间明智:除了复制/提取数据和/或密钥还需要花费时间外,此解决方案比进行foreach效率更高。同样,对于某些人来说,一个foreach似乎更有效,因为它的符号更短,但是在后台foreach也调用reset,key和next进行循环。但除此之外,它还调用有效来检查结束条件,此处由于与整数检查结合使用而被避免。

请记住,数组键只能是整数或字符串,并且严格数字字符串(例如“ 1”(但不能为“ 01”))将转换为整数。这使得检查整数键成为唯一需要的操作,除了计数之外,如果您希望数组是连续的。自然,如果is_indexed_array返回false,则该数组可以视为关联数组。我说“看过”,因为实际上它们都是。


1
这是最好的答案。“关联”或“数字”数组的定义取决于特定情况。
Pato

如果foreach的效率低于此处使用的方法,那么除了使用两个不同功能带来的不便之外,此解决方案的性能要比我的解决方案好(前面的方法)。我怀疑不是,因为建议使用foreach作为遍历数组的最快方法。
播客

7

该函数可以处理:

  • 索引中带有孔的数组(例如1,2,4,5,8,10)
  • 具有“ 0x”键的数组:例如,键“ 08”是关联键,而键“ 8”是顺序键。

这个想法很简单:如果键之一不是整数,则为关联数组,否则为顺序数组。

function is_asso($a){
    foreach(array_keys($a) as $key) {if (!is_int($key)) return TRUE;}
    return FALSE;
}

1
“如果键之一不是整数,则为关联数组,否则为顺序数组” -是吗?不,这是完全错误的。关于什么构成“关联”数组尚有争论的余地,但是“顺序”的含义非常明确,并且与所有键都是数字不同。
Mark Amery

如果其中一个键不是整数,则它本质上是关联的,但是,如果键从0-长度(数组)-1开始,则仅是顺序的。但是,如果所有键都仅被编号,则为NUMERIC。或可能不适用于需要顺序数组的许多数组函数。如果通过在其上运行array_values(array)将带孔的数字数组转换为顺序数组,则它将转换为顺序数组。
geilt's

7

我注意到此问题有两种流行的方法:一种使用array_values(),另一种使用key()。为了找出更快的速度,我编写了一个小程序:

$arrays = Array(
  'Array #1' => Array(1, 2, 3, 54, 23, 212, 123, 1, 1),
  'Array #2' => Array("Stack", 1.5, 20, Array(3.4)),
  'Array #3' => Array(1 => 4, 2 => 2),
  'Array #4' => Array(3.0, "2", 3000, "Stack", 5 => "4"),
  'Array #5' => Array("3" => 4, "2" => 2),
  'Array #6' => Array("0" => "One", 1.0 => "Two", 2 => "Three"),
  'Array #7' => Array(3 => "asdf", 4 => "asdf"),
  'Array #8' => Array("apple" => 1, "orange" => 2),
);

function is_indexed_array_1(Array &$arr) {
  return $arr === array_values($arr);
}

function is_indexed_array_2(Array &$arr) {
  for (reset($arr), $i = 0; key($arr) === $i++; next($arr))
    ;
  return is_null(key($arr));
}

// Method #1
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
  foreach ($arrays as $array) {
    $dummy = is_indexed_array_1($array);
  }
}
$end = microtime(true);
echo "Time taken with method #1 = ".round(($end-$start)*1000.0,3)."ms\n";

// Method #2
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
  foreach ($arrays as $array) {
    $dummy = is_indexed_array_2($array);
  }
}
$end = microtime(true);
echo "Time taken with method #1 = ".round(($end-$start)*1000.0,3)."ms\n";

CentOS上PHP 5.2上程序的输出如下:

方法1花费的
时间= 10.745ms 方法2 花费的时间= 18.239ms

PHP 5.3上的输出产生了相似的结果。显然使用起来array_values()要快得多。


基准不好。您没有测试大型阵列。在我的计算机上,从10K +元素开始,方法2更快。试试$arrays = Array( 'Array #1' => range(0, 50000), );
nonsensei

7

解决此问题的一种方法是搭载在上json_encode,后者已经拥有自己的内部方法,可以区分关联数组和索引数组,以便输出正确的JSON。

您可以通过检查编码后返回的第一个字符是{(关联数组)还是[(索引数组)来完成此操作。

// Too short :)
function is_assoc($arr) {
    ksort($arr);
    return json_encode($arr)[0] === '{';
}

我认为ksort()是不必要的。此解决方案有效,但必须测试$ arr是否为空以及json_encode是否失败,因此请尝试/捕获。+如果$ arr大,这并不是最佳选择。
lucbonnin18年

7

已经有很多答案,但是这是Laravel在其Arr类中依赖的方法:

/**
 * Determines if an array is associative.
 *
 * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
 *
 * @param  array  $array
 * @return bool
 */
public static function isAssoc(array $array)
{
    $keys = array_keys($array);

    return array_keys($keys) !== $keys;
}

来源:https : //github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Arr.php


1
@Casey array_keys($keys)将返回一个顺序的数字数组(0 ... X),其长度与原始数组的长度相同。例如array_keys(["a", "b", "c"]) = [0, 1, 2]; array_keys([0, 1, 2]) = [0, 1, 2](这是一个顺序数组,因为[0, 1, 2] !== [0, 1, 2])。另一个示例:(array_keys(["a" => 5, "b" => 7, "c" => 10]) = ["a", "b", "c"]; array_keys(["a", "b", "c"]) = [0, 1, 2]因为是一个关联数组["a", "b", "c"] !== [0, 1, 2])。希望很清楚(至少对我来说很难在评论中进行
详尽的

这个算法是疯狂的,简单的,易于理解的。
本义

如果您具有顺序的关联行数组,则此方法将不起作用。
lucbonnin18年

5
function array_is_assoc(array $a) {
    $i = 0;
    foreach ($a as $k => $v) {
        if ($k !== $i++) {
            return true;
        }
    }
    return false;
}

快速,简洁,高效存储。没有昂贵的比较,函数调用或数组复制。


4

通过使用xarray PHP扩展

您可以非常快地完成此操作(在PHP 5.6中快30倍以上):

if (array_is_indexed($array)) {  }

要么:

if (array_is_assoc($array)) {  }

3

我知道向这个庞大的队列添加答案有点没有意义,但是这是一个可读的O(n)解决方案,不需要重复任何值:

function isNumericArray($array) {
    $count = count($array);
    for ($i = 0; $i < $count; $i++) {
        if (!isset($array[$i])) {
            return FALSE;
        }
    }
    return TRUE;
}

而不是检查键,看看他们都是数字,你迭代的钥匙在那里为一个数值数组,并确保它们的存在。


还有一点。形式的数组[1,2,null,4]将失败,但这是正确的数组。因此,我已经在stackoverflow.com/a/25206156/501831上添加了一些附加array_key_exists检查功能)
lazycommit

-1; isset()是错误的工具,因为null@lazycommit指出,如果设置了值但为,它将返回false 。
Mark Amery

3

我的解决方案:

function isAssociative(array $array)
{
    return array_keys(array_merge($array)) !== range(0, count($array) - 1);
}

array_merge在单个数组上将重新索引所有integer键,但不能重新索引其他键。例如:

array_merge([1 => 'One', 3 => 'Three', 'two' => 'Two', 6 => 'Six']);

// This will returns [0 => 'One', 1 => 'Three', 'two' => 'Two', 2 => 'Six']

因此,如果创建了一个列表(非关联数组),['a', 'b', 'c']则将删除一个值,unset($a[1])然后array_merge调用该值,该列表将从0开始重新索引。


-1; 这是O(n)在额外的内存中使用的(因为它创建了多个新数组,其中的元素数量与一样多$array),所以答案并没有解决所提出问题的歧义,也没有确切解释其如何定义列表/非关联数组,甚至如果以上两点都不成立,那么与已经发布的其他答案相比,尚不清楚这是否会增加任何价值。
Mark Amery

3

经过一些本地基准测试,调试,编译器探测,性能分析,并滥用3v4l.org跨多个版本进行基准测试(是的,我警告停止),然后与我可以找到的每个变化进行比较...

我为您提供了有机推导的最佳平均情况最坏的情况下的关联数组测试函数,该函数在最坏的情况下比其他所有平均情况下的情况都好或更好。

/**
 * Tests if an array is an associative array.
 *
 * @param array $array An array to test.
 * @return boolean True if the array is associative, otherwise false.
 */
function is_assoc(array &$arr) {
    // don't try to check non-arrays or empty arrays
    if (FALSE === is_array($arr) || 0 === ($l = count($arr))) {
        return false;
    }

    // shortcut by guessing at the beginning
    reset($arr);
    if (key($arr) !== 0) {
        return true;
    }

    // shortcut by guessing at the end
    end($arr);
    if (key($arr) !== $l-1) {
        return true;
    }

    // rely on php to optimize test by reference or fast compare
    return array_values($arr) !== $arr;
}

https://3v4l.org/rkieX

<?php

// array_values
function method_1(Array &$arr) {
    return $arr === array_values($arr);
}

// method_2 was DQ; did not actually work

// array_keys
function method_3(Array &$arr) {
    return array_keys($arr) === range(0, count($arr) - 1);
}

// foreach
function method_4(Array &$arr) {
    $idx = 0;
    foreach( $arr as $key => $val ){
        if( $key !== $idx )
            return FALSE;
        ++$idx;
    }
    return TRUE;
}

// guessing
function method_5(Array &$arr) {
    global $METHOD_5_KEY;
    $i = 0;
    $l = count($arr)-1;

    end($arr);
    if ( key($arr) !== $l )
        return FALSE;

    reset($arr);
    do {
        if ( $i !== key($arr) )
            return FALSE;
        ++$i;
        next($arr);
    } while ($i < $l);
    return TRUE;
}

// naieve
function method_6(Array &$arr) {
    $i = 0;
    $l = count($arr);
    do {
        if ( NULL === @$arr[$i] )
            return FALSE;
        ++$i;
    } while ($i < $l);
    return TRUE;
}

// deep reference reliance
function method_7(Array &$arr) {
    return array_keys(array_values($arr)) === array_keys($arr);
}


// organic (guessing + array_values)
function method_8(Array &$arr) {
    reset($arr);
    if ( key($arr) !== 0 )
        return FALSE;

    end($arr);
    if ( key($arr) !== count($arr)-1 )
        return FALSE;

    return array_values($arr) === $arr;
}

function benchmark(Array &$methods, Array &$target, $expected){    
    foreach($methods as $method){
        $start = microtime(true);
        for ($i = 0; $i < 2000; ++$i) {
            //$dummy = call_user_func($method, $target);
            if ( $method($target) !== $expected ) {
                echo "Method $method is disqualified for returning an incorrect result.\n";
                unset($methods[array_search($method,$methods,true)]);
                $i = 0;
                break;
            }
        }
        if ( $i != 0 ) {
            $end = microtime(true);
            echo "Time taken with $method = ".round(($end-$start)*1000.0,3)."ms\n";
        }
    }
}



$true_targets = [
    'Giant array' => range(0, 500),
    'Tiny array' => range(0, 20),
];


$g = range(0,10);
unset($g[0]);

$false_targets = [
    'Large array 1' => range(0, 100) + ['a'=>'a'] + range(101, 200),
    'Large array 2' => ['a'=>'a'] + range(0, 200),
    'Tiny array' => range(0, 10) + ['a'=>'a'] + range(11, 20),
    'Gotcha array' => $g,
];

$methods = [
    'method_1',
    'method_3',
    'method_4',
    'method_5',
    'method_6',
    'method_7',
    'method_8'
];


foreach($false_targets as $targetName => $target){
    echo "==== Benchmark using $targetName expecing FALSE ====\n";
    benchmark($methods, $target, false);
    echo "\n";
}
foreach($true_targets as $targetName => $target){
    echo "==== Benchmark using $targetName expecting TRUE ====\n";
    benchmark($methods, $target, true);
    echo "\n";
}

2

这是我使用的方法:

function is_associative ( $a )
{
    return in_array(false, array_map('is_numeric', array_keys($a)));
}

assert( true === is_associative(array(1, 2, 3, 4)) );

assert( false === is_associative(array('foo' => 'bar', 'bar' => 'baz')) );

assert( false === is_associative(array(1, 2, 3, 'foo' => 'bar')) );

请注意,这不考虑特殊情况,例如:

$a = array( 1, 2, 3, 4 );

unset($a[1]);

assert( true === is_associative($a) );

抱歉,不能帮您。对于适当大小的数组,它也有些性能,因为它不会产生不必要的副本。正是这些小事情使Python和Ruby变得更加好用...:P


2
<?php

function is_list($array) {
    return array_keys($array) === range(0, count($array) - 1);
}

function is_assoc($array) {
    return count(array_filter(array_keys($array), 'is_string')) == count($array);
}

?>

这两个得分最高的示例在数组等数组上均无法正常工作 $array = array('foo' => 'bar', 1)


+1您的is_list()是IMO的最佳答案。某些人不了解时间和空间的复杂性以及本机vs PHP脚本函数的线索...
e2-e4 2012年

2

这也可以工作(演示):

function array_has_numeric_keys_only(array $array)
{
    try {
        SplFixedArray::fromArray($array, true);
    } catch (InvalidArgumentException $e) {
        return false;
    }
    return true;
}

请注意,此答案的主要目的是告知您有关的存在,SplFixedArray而不是鼓励您对此类测试使用Exception。


2

我认为标量数组的定义会因应用程序而异。也就是说,某些应用程序将需要更严格地理解标量数组,而某些应用程序将需要更宽松的意义。

下面我介绍了三种不同严格性的方法。

<?php
/**
 * Since PHP stores all arrays as associative internally, there is no proper
 * definition of a scalar array.
 * 
 * As such, developers are likely to have varying definitions of scalar array,
 * based on their application needs.
 * 
 * In this file, I present 3 increasingly strict methods of determining if an
 * array is scalar.
 * 
 * @author David Farrell <DavidPFarrell@gmail.com>
 */

/**
 * isArrayWithOnlyIntKeys defines a scalar array as containing
 * only integer keys.
 * 
 * If you are explicitly setting integer keys on an array, you
 * may need this function to determine scalar-ness.
 * 
 * @param array $a
 * @return boolean
 */ 
function isArrayWithOnlyIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    foreach ($a as $k => $v)
        if (!is_int($k))
            return false;
    return true;
}

/**
 * isArrayWithOnlyAscendingIntKeys defines a scalar array as
 * containing only integer keys in ascending (but not necessarily
 * sequential) order.
 * 
 * If you are performing pushes, pops, and unsets on your array,
 * you may need this function to determine scalar-ness.
 * 
 * @param array $a
 * @return boolean
 */ 
function isArrayWithOnlyAscendingIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    $prev = null;
    foreach ($a as $k => $v)
    {
        if (!is_int($k) || (null !== $prev && $k <= $prev))
            return false;
        $prev = $k;
    }
    return true;
}

/**
 * isArrayWithOnlyZeroBasedSequentialIntKeys defines a scalar array
 * as containing only integer keys in sequential, ascending order,
 * starting from 0.
 * 
 * If you are only performing operations on your array that are
 * guaranteed to either maintain consistent key values, or that
 * re-base the keys for consistency, then you can use this function.
 * 
 * @param array $a
 * @return boolean
 */
function isArrayWithOnlyZeroBasedSequentialIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    $i = 0;
    foreach ($a as $k => $v)
        if ($i++ !== $k)
            return false;
    return true;
}

2

源头上又快了一个。适合json_encode(和bson_encode)的编码。因此也符合javascript Array规范。

function isSequential($value){
    if(is_array($value) || ($value instanceof \Countable && $value instanceof \ArrayAccess)){
        for ($i = count($value) - 1; $i >= 0; $i--) {
            if (!isset($value[$i]) && !array_key_exists($i, $value)) {
                return false;
            }
        }
        return true;
    } else {
        throw new \InvalidArgumentException(
            sprintf('Data type "%s" is not supported by method %s', gettype($value), __METHOD__)
        );
    }
}

1
为什么issetarray_key_exists?后者不够吗?
mcfedr

@mcfedr是的,它会- isset()这里的检查完全多余。
Mark Amery

@ mcfedr,@ mark-amery由于性能原因。isset()比快array_key_exists()。看到ilia.ws/archives/...
lazycommit

@lazycommit它取决于您的数组,然后取决于是否有更好的数组,而不是它可能有很多nulls 的数组,但是也没有可能您有足够大的数组以至于会有明显的性能差异通过使用两个检查
mcfedr

2
如果你需要检查它是否会适合json_encode,你可以简单地检查字符串的第一个符号,由归国json_encode($your_arr)-无论是[还是{;-)
派拉特

2

这可以解决吗?

  public static function isArrayAssociative(array $array) {
      reset($array);
      return !is_int(key($array));
  }

需要注意的是,显然数组指针已被重置,但我想可能在遍历或使用数组之前就使用了该函数。


该函数对两者都返回falsearray("a", "b")array("a", "b" => "B")因为它仅检查第一个键。顺便说一句,is_long只是的别名is_int

1
坦率地说,我认为这在绝大多数情况下将非常有效,并且比其他方法要有效得多。如果您了解此方法的后果,并意识到它将对您有用,那么它可能是最佳选择。
Gershom,2015年

这是完全错误的。它只看第一把钥匙。
Mark Amery

@MarkAmery问题询问如何区分顺序数组和关联数组。这个答案正是这样做的,并且是所有方法中最有效的。对于问题的混合环境,具有不确定的行为是完全可以的。+1
Tobia

@Tobia我不认为大多数人会同意您的看法,[7 => 'foo', 2 => 'bar']例如将其分类为部分而非完全“连续”的“混合”数组。对我来说,这似乎是对单词的明显错误使用。
Mark Amery

2

这里的许多解决方案都是优雅美观的,但是扩展性不好,并且占用大量内存或CPU。从比较的两面来看,大多数使用此解决方案在内存中创建2个新数据点。阵列越大,使用的过程和内存就越难,而且时间越长,您就失去了短路评估的好处。我做了一些不同想法的测试。尽量避免使用array_key_exists,因为这样做代价高昂,并且还避免创建新的大型数据集进行比较。我觉得这是判断数组是否顺序的简单方法。

public function is_sequential( $arr = [] ){
    if( !is_array( $arr ) || empty( $arr ) ) return false;

    $i = 0;

    $total = count( $arr );

    foreach( $arr as $key => $value ) if( $key !== $i++ ) return false;

    return true;
}

您在主数组上运行一个计数并存储一个整数。然后,您遍历数组并在迭代计数器时检查是否完全匹配。您应该从1开始计数。如果失败,它将短路,错误时将提高性能。

最初,我是通过for循环并检查isset($ arr [$ i])来完成此操作的,但是这不会检测到需要array_key_exists的空键,并且众所周知,这是最差的速度函数。

通过foreach不断更新变量,以与迭代器一起检查,使其永远不会超过整数大小,让PHP使用它内置的内存优化,缓存和垃圾回收功能,使您的资源使用率非常低。

另外,我将争辩说,当您只需运行$ key => $ value并检查密钥时,在foreach中使用array_keys是愚蠢的。为什么要创建新的数据点?一旦抽象出阵列键,您将立即消耗更多的内存。


1

已经给出了答案,但是关于性能的信息太多。我写了这个小基准脚本,它显示了foreach方法是最快的。

免责声明:以下方法是从其他答案中复制粘贴的

<?php

function method_1(Array &$arr) {
    return $arr === array_values($arr);
}

function method_2(Array &$arr) {
    for (reset($arr), $i = 0; key($arr) !== $i++; next($arr));
    return is_null(key($arr));
}

function method_3(Array &$arr) {
    return array_keys($arr) === range(0, count($arr) - 1);
}

function method_4(Array &$arr) {
    $idx = 0;
    foreach( $arr as $key => $val ){
        if( $key !== $idx )
            return FALSE;
        $idx++;
    }
    return TRUE;
}




function benchmark(Array $methods, Array &$target){    
    foreach($methods as $method){
        $start = microtime(true);
        for ($i = 0; $i < 1000; $i++)
            $dummy = call_user_func($method, $target);

        $end = microtime(true);
        echo "Time taken with $method = ".round(($end-$start)*1000.0,3)."ms\n";
    }
}



$targets = [
    'Huge array' => range(0, 30000),
    'Small array' => range(0, 1000),
];
$methods = [
    'method_1',
    'method_2',
    'method_3',
    'method_4',
];
foreach($targets as $targetName => $target){
    echo "==== Benchmark using $targetName ====\n";
    benchmark($methods, $target);
    echo "\n";
}

结果:

==== Benchmark using Huge array ====
Time taken with method_1 = 5504.632ms
Time taken with method_2 = 4509.445ms
Time taken with method_3 = 8614.883ms
Time taken with method_4 = 2720.934ms

==== Benchmark using Small array ====
Time taken with method_1 = 77.159ms
Time taken with method_2 = 130.03ms
Time taken with method_3 = 160.866ms
Time taken with method_4 = 69.946ms

1

或者,您可以使用以下代码:

Arr::isAssoc($array)

它将检查数组是否包含任何非数字键或:

Arr:isAssoc($array, true)

检查数组是否严格顺序 (包含自动生成的int键0n-1

使用这个库。


0

除非PHP具有内置功能,否则您将无法在O(n)内完成它-枚举所有键并检查整数类型。实际上,您还想确保没有孔,因此您的算法可能如下所示:

for i in 0 to len(your_array):
    if not defined(your-array[i]):
        # this is not an array array, it's an associative array :)

但是为什么要打扰呢?只要假设数组是您期望的类型。如果不是这样,它只会在您的脸上浮现-这对您来说是动态编程!测试您的代码,一切都会很好...


1
通常,只需假设数组是所需的类型即可。但是在我的情况下,我遍历多维数组,并根据给定节点的数组类型格式化输出。
Wilco

0

我比较了数组的键和数组的array_values()结果的键之间的差异,该值将始终是具有整数索引的数组。如果键相同,则它不是关联数组。

function isHash($array) {
    if (!is_array($array)) return false;
    $diff = array_diff_assoc($array, array_values($array));
    return (empty($diff)) ? false : true;
}

-1; O(n)$arrayn项目时,这会使用额外的内存,并且书写(someboolean) ? false : true而不是!someboolean可怕的和无谓的冗长。
Mark Amery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.