PHP将所有数组都视为关联数组,因此没有任何内置函数。谁能推荐一种相当有效的方法来检查数组是否“是一个列表”(只包含从0开始的数字键)?

基本上,我希望能够区分这些:

$sequentialArray = [
    'apple', 'orange', 'tomato', 'carrot'
];

这:

$assocArray = [
    'fruit1' => 'apple',
    'fruit2' => 'orange',
    'veg1' => 'tomato',
    'veg2' => 'carrot'
];

当前回答

function is_array_assoc($foo) {
    if (is_array($foo)) {
        return (count(array_filter(array_keys($foo), 'is_string')) > 0);
    }
    return false;
}

其他回答

大多数答案的时间/空间复杂度都不是最佳的,或者语义正在发生变化。所以,这里有另一个最快和功能最正确的解决方案:

function is_sequential_array(Array &$a) {
    $n = count($a);
    for($i=0; $i<$n; $i++) {
        if(!array_key_exists($i, $a)) {
            return false;
        }
    }
    return true;
}

与其他答案相比,这个答案有以下优点:

O(1)的空间复杂度(这里很多答案使用O(n)个空间!) 不对键应用相等性(这是一种不必要且代价高昂的操作) 将输入数组视为不可变(许多答案通过应用突变函数隐式创建了一个副本) 使用函数array_key_exists代替isset(记住,isset额外检查'is not null',从而改变语义) 最坏情况时间复杂度为O(n)(这里许多答案的最佳情况时间复杂度为O(n))

这个函数可以处理:

索引有孔的数组(例如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;
}

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

这是因为array_keys和array_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;
}

还要注意,如果你不关心区分稀疏数组和关联数组,你可以简单地从两个if块中返回'assoc'。

最后,虽然这可能看起来没有本页上的许多“解决方案”那么“优雅”,但实际上它的效率要高得多。几乎任何关联数组都会立即被检测到。只有索引数组才会被彻底检查,上面列出的方法不仅会彻底检查索引数组,还会复制它们。

有时你可以只检查第一个数组的Key是否为0。

$isSequential = array_keys($arr)[0] ===

,或更快,但更冗长的版本:

重置(arr);$isSequential = key($arr) === 0

修改最流行的答案。 这需要更多的处理,但更准确。

<?php
//$a is a subset of $b
function isSubset($a, $b)
{
    foreach($a =>$v)
        if(array_search($v, $b) === false)
            return false;

    return true;

    //less effecient, clearer implementation. (uses === for comparison)
    //return array_intersect($a, $b) === $a;
}

function isAssoc($arr)
{
    return !isSubset(array_keys($arr), range(0, count($arr) - 1));
}

var_dump(isAssoc(array('a', 'b', 'c'))); // false
var_dump(isAssoc(array(1 => 'a', 0 => 'b', 2 => 'c'))); // false
var_dump(isAssoc(array("0" => 'a', "1" => 'b', "2" => 'c'))); // false 
//(use === in isSubset to get 'true' for above statement)
var_dump(isAssoc(array("a" => 'a', "b" => 'b', "c" => 'c'))); // true
?>