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

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

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

这:

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

当前回答

你问了两个不完全等价的问题:

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

考虑一下哪些行为是你真正需要的。(也许对你来说,这两种方法都可以。)

第一个问题(简单地检查所有键是否都是数字)由kurO船长回答得很好。

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

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

其他回答

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

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

// Too short :)
function is_assoc($arr) {
    ksort($arr);
    return json_encode($arr)[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 :)

但为什么要麻烦呢?只需假设数组是您所期望的类型。如果不是,它就会在你面前爆炸——这就是动态编程!测试你的代码,一切都会很好……

这个问题实际上在php数组中是没有用的,因为php的本质是数组不应该是完全关联的或索引的,它可以是两者的组合,用户定义和赋值数组的方式可以是两者的组合。参见下面的示例

$y= array(5);
$y["0x"]="n";
$y["vbg"]="12132";
$y[1] = "k";

var_dump($y); //this will output 4 element array

echo "</br>" .$y["0x"]."</br>".$y[0];

for($x=0;$x<sizeof($y);$x++){ // this will output all index elements & gives error after that
    echo "</br> index elements ".$y[$x];
}

正确的问题是,数组中的所有元素都是关联的还是下标的。如果你真的知道它只能是关联数组或索引数组而不是两者的组合,你可以简单地使用这个方法来确定它是索引数组还是关联数组。

function AssocTest(&$arr){
    if(is_array($arr)){

        reset($arr); // reset pointer to first element of array

        if(gettype(key($arr)) == "string"){ //get the type(nature) of first element key 
            return true;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

你可以正常使用它

echo(AssocTest($y)?  "Associative array": "Not an Associative array/ Not an array at all");

重要的是,evan,你已经初始化了一个关联数组但是你给关联数组的名字只是数字当它被PHP读取时它会被当作一个索引数组如果你没有显式地给字符串名字。看看下面的例子。

$y["0"]="n";
$y["1"]="12132";
$y["22"] = "k";

//both will get the same output
echo "<br/> s0 ".$y["22"];
echo "<br/> s0 ".$y[22];

for($x=0;$x<count($y);$x++){
   echo "<br/> arr ".$y[$x]; // this will output up to 2nd element and give an error after

}

如果你需要确保数组中的所有元素都被精确索引,或者是关联的,没有其他方法,只能对所有元素赋真,并检查每个元素键通过生成索引数组,就像这里很多人发布的那样。

function fullAssocTest(&$arr)
{
    if(is_array($arr)){
        return (array_keys($arr) !== range(0, count($arr) - 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

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