Thursday, May 31, 2012

PHP Arrays: A good way to check if an array is associative or sequential?


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

26 comments:

  1. This will do it for you

    <?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

    ?>

    ReplyDelete
  2. Surely this is a better alternative.

    <?php
    $arr = array(1,2,3,4);
    $isIndexed = array_values($arr) === $arr;

    ?>

    ReplyDelete
  3. I stumble upon this problem recently, and this is how I check whether an array is assoc or not

    function 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

    ReplyDelete
  4. function checkAssoc($array){
    return ctype_digit( implode('', array_keys($array) ) );
    }

    ReplyDelete
  5. Many commenters in this question don't understand how arrays work in PHP. From the array documentation:


    A 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 "").

    ReplyDelete
  6. Speed-wise:

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


    Memory-wise:

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

    ReplyDelete
  7. Actually the most efficient way is thus:

    function 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).

    ReplyDelete
  8. 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).

    In 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.

    ReplyDelete
  9. This function can handle:


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

    ReplyDelete
  10. Could this be the solution?

    public 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.

    ReplyDelete
  11. Best function to detect associative array (hash array)

    <?php
    function is_assoc($arr) { return (array_values($arr) !== $arr); }
    ?>

    ReplyDelete
  12. function is_associative($arr) {
    return (array_merge($arr) !== $arr || !is_numeric(implode(array_keys($arr))));
    }

    ReplyDelete
  13. I just use the key() function. Observe:

    <?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.

    ReplyDelete
  14. 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;
    }

    ReplyDelete
  15. <?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);
    }

    ?>


    Both of these examples, which scored the most points do not work correctly with arrays like $array = array('foo' => 'bar', 1)

    ReplyDelete
  16. Yet another way to do this.

    function 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;

    }

    ReplyDelete
  17. function isAssoc($arr)
    {
    $a = array_keys($arr);
    for($i = 0, $t = count($a); $i < $t; $i++)
    {
    if($a[$i] != $i)
    {
    return false;
    }
    }
    return true;
    }

    ReplyDelete
  18. 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
    ?>

    ReplyDelete
  19. Another variant not shown yet, as it's simply not accepting numerical keys, but I like Greg's one very much :

    /* Returns true if $var associative array */
    function is_associative_array( $array ) {
    return is_array($array) && !is_numeric(implode('', array_keys($array)));
    }

    ReplyDelete
  20. Here's the method I use:

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


    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

    ReplyDelete
  21. Simple and performance friendly solution which only checks the first key.

    function isAssoc($arr = NULL)
    {
    if ($arr && is_array($arr))
    {
    foreach ($arr as $key => $val)
    {
    if (is_numeric($key)) { return true; }

    break;
    }
    }

    return false;
    }

    ReplyDelete
  22. I met this problem once again some days ago and i thought to take advantage of the array_merge special property:


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

    ReplyDelete
  23. My solution is to get keys of an array like below and check that if the key is not integer:

    private 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.

    ReplyDelete
  24. 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:

    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 :)


    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...

    ReplyDelete
  25. One cheap and dirty way would be to check like this:

    isset($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");

    ReplyDelete
  26. If your looking for just non-numeric keys (no matter the order) then you may want to try

    function IsAssociative($array)
    {
    return preg_match('/[a-z]/i', implode(array_keys($array)));
    }

    ReplyDelete