如何检查PHP数组是联想还是顺序?
PHP将所有数组视为关联,所以没有任何内置函数。 任何人都可以推荐一个相当有效的方式来检查数组是否只包含数字键?
基本上,我想能够区分这一点:
$sequentialArray = array('apple', 'orange', 'tomato', 'carrot');
和这个:
$assocArray = array('fruit1' => 'apple', 'fruit2' => 'orange', 'veg1' => 'tomato', 'veg2' => 'carrot');
你问了两个不完全相同的问题:
- 首先,如何确定数组是否只有数字键
- 其次,如何确定一个数组是否有连续的数字键,从0开始
考虑你实际需要哪些行为。 (这可能是为了你的目的。
第一个问题(简单地检查所有键是数字) 由kurO上尉很好地回答 。
对于第二个问题(检查数组是否为零索引和顺序),可以使用以下函数:
function isAssoc(array $arr) { if (array() === $arr) return false; return array_keys($arr) !== range(0, count($arr) - 1); } var_dump(isAssoc(array('a', 'b', 'c'))); // false var_dump(isAssoc(array("0" => 'a', "1" => 'b', "2" => 'c'))); // false var_dump(isAssoc(array("1" => 'a', "0" => 'b', "2" => 'c'))); // true var_dump(isAssoc(array("a" => 'a', "b" => 'b', "c" => 'c'))); // true
仅仅检查数组是否有非整数键(而不是数组是按顺序索引还是无索引):
function has_string_keys(array $array) { return count(array_filter(array_keys($array), 'is_string')) > 0; }
如果至less有一个string键, $array
将被视为一个关联数组。
当然这是一个更好的select。
<?php $arr = array(1,2,3,4); $isIndexed = array_values($arr) === $arr;
在这个问题中的许多评论者不明白数组如何在PHP中工作。 从arrays文档 :
一个键可以是一个整数或一个string。 如果一个键是一个整数的标准表示,它将被解释为这样(即“8”将被解释为8,而“08”将被解释为“08”)。 浮点键被截断为整数。 索引和关联数组types在PHP中是相同的types,它们都可以包含整数和string索引。
换句话说,不存在数组键“8”,因为它总是(默默地)转换为整数8.所以试图区分整数和数字串是不必要的。
如果你想要最有效的方法来检查一个数组的非整数键而不复制数组的一部分(像array_keys()那样)或所有它(像foreach一样):
for (reset($my_array); is_int(key($my_array)); next($my_array)); $onlyIntKeys = is_null(key($my_array));
这是有效的,因为key()在当前数组位置无效时返回NULL,并且NULL永远不可能是有效的键(如果您尝试使用NULL作为数组键,它将被默认转换为“”)。
正如OP所述 :
PHP将所有数组视为关联
(恕我直言)编写一个函数来检查一个数组是否是关联的是不太明智的。 所以首先要做的是 : PHP数组中的关键是什么 ?
密钥可以是整数或string 。
这意味着有三种可能的情况:
- 情况1.所有的键都是数字 / 整数 。
- 情况2.所有的键都是string 。
- 情况3.一些键是string ,一些键是数字 / 整数 。
我们可以检查每个案件与以下function。
情况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:所有密钥都是string 。
注意 : 这个函数也为空数组返回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.一些键是string ,一些键是数字 / 整数 。
注意 : 这个函数也为空数组返回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 。
现在,为了使arrays成为我们都习惯的“真正的”arrays ,意思是:
- 它的键都是数字 / 整数 。
- 它的键是连续的 (即通过步骤1增加)。
- 它的键从零开始 。
我们可以检查以下function。
案例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.
string键
这些数组的键是string :
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.comhttp://img.dovov.comnav_logo114.png") => "b"); // Strings may even be binary!
整数键看起来像string
如果你认为array("13" => "b")
的键array("13" => "b")
是一个string , 那么你错了 。 从这里 doc:
包含有效整数的string将被转换为整数types。 例如,键“8”实际上将被存储在8以下。另一方面,“08”不会被投射,因为它不是有效的十进制整数。
例如,这些数组的键是整数 :
array("13" => "b"); array("-13" => "b"); // Negative, ok.
但是这些数组的关键是string :
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.
而且,根据文件 ,
整数的大小是依赖于平台的,尽pipe最大值约为20亿是通常的值(这是32位的)。 除了总是32位的Windows外,64位平台的最大值通常约为9E18。 PHP不支持无符号整数。
所以这个数组的关键可能是也可能不是一个整数 – 这取决于你的平台。
array("60000000000" => "b"); // Array key could be integer or string, it can fit into a 64-bit (but not 32-bit) integer.
更糟糕的是,如果整数接近2 31 = 2,147,483,648的边界(见错误51430 , 错误52899 ),那么PHP往往会出错 。 例如,在我的本地环境(Windows 7上的XAMPP 1.7.7上的PHP 5.3.8)中, var_dump(array("2147483647" => "b"))
给出
array(1) { [2147483647]=> string(1) "b" }
但在键盘上的这个现场演示 (PHP 5.2.5),相同的expression给出
array(1) { ["2147483647"]=> string(1) "b" }
因此,密钥是一个环境中的整数 ,而另一个string中的一个整数 ,即使2147483647
是有符号的32位整数 。
速度明智的:
function isAssoc($array) { return ($array !== array_values($array)); }
内存明智的:
function isAssoc($array) { $array = array_keys($array); return ($array !== array_keys($array)); }
function checkAssoc($array){ return ctype_digit( implode('', array_keys($array) ) ); }
其实最有效的方法是:
function is_assoc($array){ $keys = array_keys($array); return $keys !== array_keys($keys); }
这是有效的,因为它将键(对于一个连续的数组总是0,1,2等)与键的键(它们总是为0,1,2等)进行比较。
我已经使用array_keys($obj) !== range(0, count($obj) - 1)
和array_values($arr) !== $arr
(这是双方的对方,虽然第二个便宜第一个),但都失败了非常大的数组。
这是因为array_keys
和array_values
都是非常昂贵的操作(因为它们构build了一个与原始大小相当的全新数组)。
下面的函数比上面提供的方法更强大:
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'
。
最后,虽然这个页面上的“解决scheme”看起来可能不那么“优雅”,但在实践中效率要高得多。 几乎任何关联数组将立即被检测到。 只有索引数组才会被彻底检查,上面概述的方法不仅检查索引数组,而且还复制它们。
我认为以下两个函数是检查“数组是关联还是数字”的最好方法。 由于“数字”只能表示数字键或者只能表示连续的数字键,所以下面列出了两个function来检查以下任一情况:
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,如果所有的键都是整数。 如果不是,则循环会过早结束,因为键是stringtypes,for循环后面的语句将返回false。 后面的函数在每次比较之后还加一个到$ base,以便能够检查下一个键是否是正确的值。 严格的比较使得它也检查密钥是否是整型。 for循环的第一部分中的$ base =(int)$ base部分可以省略$ base时省略,或者如果您确保只使用整数调用它。 但是因为我不能确定每个人,所以我把它放在里面。无论如何,声明只被执行一次。 我认为这是最有效的解决scheme:
- 内存明智:不复制数据或密钥范围。 做一个array_values或array_keys可能看起来更短(less代码),但要记住一旦你打电话后面发生了什么。 是的,还有更多(可见的)陈述比其他一些解决scheme,但这不是什么重点,是吗?
- 时间明智:除了复制/提取数据和/或密钥也需要时间,这个解决scheme比做foreach更有效率。 再次,一个foreach可能对一些看起来更有效,因为它的表示法比较短,但是在后台,foreach也会调用reset,key和next来做循环。 但是另外它也被称为有效的检查结束条件,这是由于与整数检查的组合而避免的。
请记住,一个数组键只能是一个整数或一个string,一个严格的数字string,如“1”(但不是“01”)将被翻译成一个整数。 这是什么使检查一个整数键除了计数,如果你想数组是顺序唯一需要的操作。 当然,如果is_indexed_array返回false,则该数组可以被视为关联。 我说'看过',因为实际上他们都是。
这个函数可以处理:
- 在索引中有孔(例如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; }
我注意到这个问题的两个stream行的方法:一个使用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()
要快得多。
除非PHP有一个内置的,否则你将无法做到小于O(n) – 枚举所有的键和检查整数types。 事实上,你也想确保没有漏洞,所以你的algorithm可能看起来像:
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 :)
但为什么要麻烦? 假设这个数组是你期望的types。 如果不是的话,它会炸毁你的脸 – 这是你的dynamic编程! testing你的代码,一切都会好的…
这也可以( 演示 ):
function array_has_numeric_keys_only(array $array) { try { SplFixedArray::fromArray($array, true); } catch (InvalidArgumentException $e) { return false; } return true; }
请注意,这个答案的要点是告诉你有关SplFixedArray
的存在,而不是鼓励你使用例外进行这些types的testing。
通过使用xarray PHP扩展
您可以非常快速地执行此操作(PHP 5.6中的速度提高了约30倍以上):
if (array_is_indexed($array)) { }
要么:
if (array_is_assoc($array)) { }
我的解决scheme
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']
因此,如果一个列表(一个非关联数组)被创build['a', 'b', 'c']
那么一个值被删除unset($a[1])
然后array_merge
被调用,列表被重新索引0。
这是我使用的方法:
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) );
对不起,帮不了你。 对于大小适中的arrays来说,它也有一定的性能,因为它不会造成不必要的副本。 正是这些小小的东西让Python和Ruby更好地写入…:P
<?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)
我认为标量数组的定义会因应用程序而异。 也就是说,一些应用程序将需要更严格的标量数组是否合格,而一些应用程序则需要更宽松的意义。
下面我提出3种不同严格的方法。
<?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; }
Could this be the solution?
public static function isArrayAssociative(array $array) { reset($array); return !is_int(key($array)); }
The caveat is obviously that the array cursor is reset but I'd say probably the function is used before the array is even traversed or used.
function array_is_assoc(array $a) { $i = 0; foreach ($a as $k => $v) { if ($k !== $i++) { return true; } } return false; }
Fast, concise, and memory efficient. No expensive comparisons, function calls or array copying.
I know it's a bit pointless adding an answer to this huge queue, but here's a readable O(n) solution that doesn't require duplicating any values:
function isNumericArray($array) { $count = count($array); for ($i = 0; $i < $count; $i++) { if (!isset($array[$i])) { return FALSE; } } return TRUE; }
Rather than check the keys to see if they are all numeric, you iterate over the keys that would be there for a numeric array and make sure they exist.
One more fast from source . Fit encoding of json_encode
(and bson_encode
). So has javascript Array compliance.
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__) ); } }
answers are already given but there's too much disinformation about performance. I wrote this little benchmark script that shows that the foreach method is the fastest.
Disclaimer: following methods were copy-pasted from the other answers
<?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
There are many answers already, but here is the method that Laravel relies on within its Arr class:
/** * 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; }
Source: https://github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Arr.php
One way to approach this is to piggyback on json_encode
, which already has its own internal method of differentiating between an associative array and an indexed array in order to output the correct JSON.
You can do this by checking to see if the first character returned after encoding is a {
(associative array) or a [
(indexed array).
echo substr(json_encode($the_array), 0, 1) == '{' ? 'yes' : 'no';
I compare the difference between the keys of the array and the keys of the result of array_values() of the array, which will always be an array with integer indices. If the keys are the same, it's not an associative array.
function isHash($array) { if (!is_array($array)) return false; $diff = array_diff_assoc($array, array_values($array)); return (empty($diff)) ? false : true; }
Modification on the most popular answer.
This takes a little more processing, but is more accurate.
<?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 ?>
In my opinion, an array should be accepted as associative if any of its keys is not integer eg float numbers and empty string ''.
Also non-sequenced integers has to be seen as associative like (0,2,4,6) because these kind of arrays cannot be used with for loops by this way:
$n =count($arr); for($i=0,$i<$n;$i++)
The second part of the function below does check if the keys are indexed or not.It also works for keys with negative values. For example (-1,0,1,2,3,4,5)
count() = 7 , max = 5, min=-1 if( 7 == (5-(-1)+1 ) // true return false; // array not associative /** * isAssoc Checks if an array is associative * @param $arr reference to the array to be checked * @return bool */ function IsAssoc(&$arr){ $keys= array_keys($arr); foreach($keys as $key){ if (!is_integer($key)) return true; } // if all keys are integer then check if they are indexed if(count($arr) == (max($keys)-min($keys)+1)) return false; else return true; }
function is_array_assoc($foo) { if (is_array($foo)) { return (count(array_filter(array_keys($foo), 'is_string')) > 0); } return false; }