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

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

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

这:

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

当前回答

我已经使用了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'。

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

其他回答

速度明智:

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

Memory-wise:

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

如OP所述:

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

恕我直言,写一个函数来检查数组是否关联是不太明智的。首先,PHP数组中的键是什么?:

键可以是整数,也可以是字符串。

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

案例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;
}

由此可见:

如果值不是数组,3个函数都返回false。 如果值为空数组,所有3个函数都返回true (根据定义,“空集合是任何集合a的子集,因为它的所有元素都属于a”)。 如果值是非空数组,则只有一个函数返回true。


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

它的键都是数字/整数。 它的键是顺序的(即按步骤1递增)。 它的键从零开始。

我们可以用下面的函数来检查。

例3。键包括数字/整数、顺序和从零开始。

注意:该函数对于空数组也返回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!

看起来像字符串的整数键

如果你认为数组中的键("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.

更糟糕的是,如果整数接近231 = 2,147,483,648边界(参见bug 51430, bug 52899), PHP就容易出现bug。例如,在我的本地环境(PHP 5.3.8 on XAMPP 1.7.7 on Windows 7)中,var_dump(array("2147483647" => "b")给出

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

但是在codepad (PHP 5.2.5)上的这个现场演示中,相同的表达式给出

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

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

经过一些本地基准测试、调试、编译器探测、分析和滥用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";
}

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

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

这是因为它将键(对于顺序数组来说总是0,1,2等)与键的键(总是0,1,2等)进行比较。

Laravel使用这种方法。

解决这个问题的一种方法是利用json_encode,它已经有自己的内部方法来区分关联数组和索引数组,以便输出正确的JSON。

可以通过检查编码后返回的第一个字符是{(关联数组)还是[(索引数组)来做到这一点。

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