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

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

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

这:

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

当前回答

实际上,我发现自己遇到了类似的情况,试图获取一个数组并将其解析为XML。XML元素名称不能以数字开头——我发现的代码片段不能正确处理带有数字索引的数组。

我的具体情况如下

上面@null (http://stackoverflow .com/a/173589/293332)提供的答案实际上非常接近。我对它被否决感到沮丧:那些不懂正则表达式的人过着非常令人沮丧的生活。

总之,根据他的回答,我得出了以下结论:

/** 
 * Checks if an array is associative by utilizing REGEX against the keys
 * @param   $arr    <array> Reference to the array to be checked
 * @return  boolean
 */     
private function    isAssociativeArray( &$arr ) {
    return  (bool)( preg_match( '/\D/', implode( array_keys( $arr ) ) ) );
}

更多细节请参见PCRE转义序列和PCRE语法页面。

我的特殊情况

这是我正在处理的一个示例数组:

Case A
return  array(
    "GetInventorySummary"  => array(
        "Filters"  => array( 
            "Filter"  => array(
                array(
                    "FilterType"  => "Shape",
                    "FilterValue"  => "W",
                ),
                array(
                    "FilterType"  => "Dimensions",
                    "FilterValue"  => "8 x 10",
                ),
                array(
                    "FilterType"  => "Grade",
                    "FilterValue"  => "A992",
                ),
            ),
        ),
        "SummaryField"  => "Length",
    ),
);

问题是过滤器键是可变的。例如:

Case B
return  array(
    "GetInventorySummary"  => array(
        "Filters"  => array( 
            "Filter"  => array(
                "foo"   =>  "bar",
                "bar"   =>  "foo",
            ),
        ),
        "SummaryField"  => "Length",
    ),
);

为什么我需要协会。检查数组

如果我要转换的数组像情况A一样,我想要返回的是:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GetInventorySummary>
    <Filters>
        <Filter>
            <FilterType>Shape</FilterType>
            <FilterValue>W</FilterValue>
        </Filter>
        <Filter>
            <FilterType>Dimensions</FilterType>
            <FilterValue>8 x 10</FilterValue>
        </Filter>
        <Filter>
            <FilterType>Grade</FilterType>
             <FilterValue>A992</FilterValue>
        </Filter>
    </Filters>
    <SummaryField>Length</SummaryField>
</GetInventorySummary>

... 然而,如果我要转换的数组像Case B一样,我想要返回的是:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GetInventorySummary>
    <Filters>
        <Filter>
            <foo>bar</foo>
            <bar>foo</bar>
        </Filter>
    </Filters>
    <SummaryField>Length</SummaryField>
</GetInventorySummary>

其他回答

经过一些本地基准测试、调试、编译器探测、分析和滥用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 IsAssociative($array)
{
    return preg_match('/[a-z]/i', implode(array_keys($array)));
}

A lot of the solutions here are elegant and pretty, but don't scale well, and are memory intensive or CPU intensive. Most are creating 2 new data points in memory with this solution from both sides of the comparison. The larger the array the harder and longer the process and memory used, and you lose the benefit of short circuit evaluation. I Did some testing with a few different ideas. Trying to avoid array_key_exists as it is costly, and also avoiding creating new large datasets to compare. I feel this is a simple way to tell if an array is sequential.

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]),但这将不检测null键,这需要array_key_exists,我们知道,这是最糟糕的函数使用速度。

通过foreach不断更新变量,并检查迭代器,永远不会超过它的整数大小,让PHP使用它内置的内存优化,缓存和垃圾收集,以保持非常低的资源使用。

此外,我认为在foreach中使用array_keys是愚蠢的,因为您可以简单地运行$key => $value并检查键。为什么要创建新的数据点?一旦抽象出数组键,就会立即消耗更多内存。

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

首先,如何确定数组是否只有数字键 其次,如何确定数组是否具有从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

我认为下面两个函数是最好的方法去检查'如果一个数组是结合或数字'。由于'numeric'可能只表示数字键或顺序数字键,下面列出了两个函数来检查这两种情况:

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));
}

The first function checks if each key is an integer value. The second function checks if each key is an integer value and in addition checks if all keys are sequential starting at $base, which defaults to 0 and thus can be omitted if you do not need to specify another base value. key($my_array) returns null if the read pointer is moved past the end of the array, which is what ends the for loop and makes the statement after the for loop return true if all keys were integer. If not, the loop ends prematurely because a key is of type string, and the statement after the for loop will return false. The latter function in addition adds one to $base after each compare, to be able to check if the next key is of the correct value. The strict compare makes it also check if the key is of type integer. The $base = (int) $base part in the first section of the for loop can be left out when $base is omitted or if you make sure it is only called using an integer. But since I can't be sure for everybody, I left it in. The statement is executed only once, anyway. I think these are the most efficient solutions:

Memory wise: No copying of data or key ranges. Doing an array_values or array_keys may seem shorter (less code) but keep in mind what goes on in the background once you make that call. Yes there are more (visible) statements than in some other solutions, but that is not what counts, is it? Time wise: Besides the fact that copying/extracting data and/or keys also takes time, this solution is more efficient than doing a foreach. Again a foreach may seem more efficient to some because it is shorter in notation, but in the background foreach also calls reset, key and next to do it's looping. But in addition it also calls valid to check the end condition, which is avoided here due to the combination with the integer check.

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