PHP treats all arrays as associative, so there aren't any built in functions. Can anyone recommend a fairly efficient way to check if an array contains only numeric keys?
Basically, I want to be able to differentiate between this:
$sequentialArray = array('apple', 'orange', 'tomato', 'carrot');
and this:
$assocArray = array('fruit1' => 'apple',
'fruit2' => 'orange',
'veg1' => 'tomato',
'veg2' => 'carrot');
Source: Tips4all
This will do it for you
ReplyDelete<?php
function isAssoc($arr)
{
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
?>
Surely this is a better alternative.
ReplyDelete<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;
?>
I stumble upon this problem recently, and this is how I check whether an array is assoc or not
ReplyDeletefunction is_assoc($array) {
return (bool)count(array_filter(array_keys($array), 'is_string'));
}
that function assumes:
is_array($array) == true
if there is at least one string key, $array will be regarded as associative array
hope that helps
function checkAssoc($array){
ReplyDeletereturn ctype_digit( implode('', array_keys($array) ) );
}
Many commenters in this question don't understand how arrays work in PHP. From the array documentation:
ReplyDeleteA key may be either an integer or a string. If a key is the standard representation of an integer, it will be interpreted as such (i.e. "8" will be interpreted as 8, while "08" will be interpreted as "08"). Floats in key are truncated to integer. The indexed and associative array types are the same type in PHP, which can both contain integer and string indices.
In other words, there is no such thing as an array key of "8" because it will always be (silently) converted to the integer 8. So trying to differentiate between integers and numeric strings is unnecessary.
If you want the most efficient way to check an array for non-integer keys without making a copy of part of the array (like array_keys() does) or all of it (like foreach does):
for (reset($my_array); is_int(key($my_array)); next($my_array));
$onlyIntKeys = is_null(key($my_array));
This works because key() returns NULL when the current array position is invalid and NULL can never be a valid key (if you try to use NULL as an array key it gets silently converted to "").
Speed-wise:
ReplyDeletefunction isAssoc($array)
{
return ($array !== array_values($array));
}
Memory-wise:
function isAssoc($array)
{
$array = array_keys($array); return ($array !== array_keys($array));
}
Actually the most efficient way is thus:
ReplyDeletefunction is_assoc($a){
return (array_keys($a) != array_keys(array_keys($a)));
}
This works because it compares the keys (which for a sequential array are always 0,1,2 etc) to the keys of the keys (which will always be 0,1,2 etc).
I've used both array_keys($obj) !== range(0, count($obj) - 1) and array_values($arr) !== $arr (which are duals of each other, although the second is cheaper than the first) but both fail for very large arrays. Interestingly enough I encountered this while trying to figure out why json_encode was failing for a large array, so I wrote my own replacement for it which failed as well (and this was where it failed).
ReplyDeleteIn essence, array_keys and array_values are both very costly operations (since they build a whole new array of size roughly that of the original).
The following function is more robust than the methods provided above, but not perfect:
function array_type( $obj ){
$last_key = -1;
$type = 'index';
foreach( $obj as $key => $val ){
if( !is_int( $key ) ){
return 'assoc';
}
if( $key !== $last_key + 1 ){
$type = 'sparse';
}
$last_key = $key;
}
return $type;
}
Note that if I wrote foreach( array_keys( $obj ) as $key ){ } then it would fail just as quickly as the earlier described methods.
Also note that if you don't care to differentiate sparse arrays from associative arrays you can simply return 'assoc' for !is_int($key) and $key.
Finally, while this might seem much less "elegant" than a lot of "solutions" on this page, in practice it is vastly more efficient. Almost any associative array will be detected instantly. Only indexed arrays will get checked exhaustively, and the methods outlined above not only check indexed arrays exhaustively, they duplicate them.
This function can handle:
ReplyDeletearray with holes in index (e.g. 1,2,4,5,8,10)
array with "0x" keys: e.g. key '08' is associative while key '8' is sequential.
the idea is simple: if one of the keys is NOT an integer, it is associative array, otherwise it's sequential.
function is_asso($a){
foreach(array_keys($a) as $key) {if (!is_int($key)) return TRUE;}
return FALSE;
}
Could this be the solution?
ReplyDeletepublic static function isArrayAssociative(array $array) {
reset($array);
$k = key($array);
return !(is_int($k) || is_long($k));
}
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.
Best function to detect associative array (hash array)
ReplyDelete<?php
function is_assoc($arr) { return (array_values($arr) !== $arr); }
?>
function is_associative($arr) {
ReplyDeletereturn (array_merge($arr) !== $arr || !is_numeric(implode(array_keys($arr))));
}
I just use the key() function. Observe:
ReplyDelete<?php
var_dump(key(array('hello'=>'world', 'hello'=>'world'))); //string(5) "hello"
var_dump(key(array('world', 'world'))); //int(0)
var_dump(key(array("0" => 'a', "1" => 'b', "2" => 'c'))); //int(0) who makes string sequetial keys anyway????
?>
Thus, just by checking for false, you can determine whether an array is associative or not.
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.
ReplyDeletefunction isHash($array) {
if (!is_array($array)) return false;
$diff = array_diff_assoc($array, array_values($array));
return (empty($diff)) ? false : true;
}
<?php
ReplyDeletefunction 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);
}
?>
Both of these examples, which scored the most points do not work correctly with arrays like $array = array('foo' => 'bar', 1)
Yet another way to do this.
ReplyDeletefunction array_isassosciative($array)
{
// Create new Blank Array
$compareArray = array();
// Make it the same size as the input array
array_pad($compareArray, count($array), 0);
// Compare the two array_keys
return (count(array_diff_keys($array, $compareArray))) ? true : false;
}
function isAssoc($arr)
ReplyDelete{
$a = array_keys($arr);
for($i = 0, $t = count($a); $i < $t; $i++)
{
if($a[$i] != $i)
{
return false;
}
}
return true;
}
Modification on the most popular answer.
ReplyDeleteThis 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
?>
Another variant not shown yet, as it's simply not accepting numerical keys, but I like Greg's one very much :
ReplyDelete/* Returns true if $var associative array */
function is_associative_array( $array ) {
return is_array($array) && !is_numeric(implode('', array_keys($array)));
}
Here's the method I use:
ReplyDeletefunction 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')) );
Note that this doesn't account for special cases like:
$a = array( 1, 2, 3, 4 );
unset($a[1]);
assert( true === is_associative($a) );
Sorry, can't help you with that. It's also somewhat performant for decently sized arrays, as it doesn't make needless copies. It is these little things that makes Python and Ruby so much nicer to write in... :P
Simple and performance friendly solution which only checks the first key.
ReplyDeletefunction isAssoc($arr = NULL)
{
if ($arr && is_array($arr))
{
foreach ($arr as $key => $val)
{
if (is_numeric($key)) { return true; }
break;
}
}
return false;
}
I met this problem once again some days ago and i thought to take advantage of the array_merge special property:
ReplyDeleteIf the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended. Values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array.
So why not to use:
function Is_Indexed_Arr($arr){
$arr_copy = $arr;
if((2*count($arr)) == count(array_merge($arr, $arr_copy))){
return 1;
}
return 0;
}
My solution is to get keys of an array like below and check that if the key is not integer:
ReplyDeleteprivate function is_hash($array) {
foreach($array as $key => $value) {
return ! is_int($key);
}
return false;
}
It is wrong to get array_keys of a hash array like below:
array_keys(array(
"abc" => "gfb",
"bdc" => "dbc"
)
);
will output:
array(
0 => "abc",
1 => "bdc"
)
So, it is not a good idea to compare it with a range of numbers as mentioned in top rated answer. It will always say that it is a hash array if you try to compare keys with a range.
Unless PHP has a builtin for that, you won't be able to do it in less than O(n) - enumerating over all the keys and checking for integer type. In fact, you also want to make sure there are no holes, so your algorithm might look like:
ReplyDeletefor i in 0 to len(your_array):
if not defined(your-array[i]):
# this is not an array array, it's an associative array :)
But why bother? Just assume the array is of the type you expect. If it isn't, it will just blow up in your face - that's dynamic programming for you! Test your code and all will be well...
One cheap and dirty way would be to check like this:
ReplyDeleteisset($myArray[count($myArray) - 1])
...you might get a false positive if your array is like this:
$myArray = array("1" => "apple", "b" => "banana");
A more thorough way might be to check the keys:
function arrayIsAssociative($myArray) {
foreach (array_keys($myArray) as $ind => $key) {
if (!is_numeric($key) || (isset($myArray[$ind + 1]) && $myArray[$ind + 1] != $key + 1)) {
return true;
}
}
return false;
}
// this will only return true if all the keys are numeric AND sequential, which
// is what you get when you define an array like this:
// array("a", "b", "c", "d", "e");
or
function arrayIsAssociative($myArray) {
$l = count($myArray);
for ($i = 0; $i < $l, ++$i) {
if (!isset($myArray[$i])) return true;
}
return false;
}
// this will return a false positive on an array like this:
$x = array(1 => "b", 0 => "a", 2 => "c", 4 => "e", 3 => "d");
If your looking for just non-numeric keys (no matter the order) then you may want to try
ReplyDeletefunction IsAssociative($array)
{
return preg_match('/[a-z]/i', implode(array_keys($array)));
}