12

I'd like to be able to pass an array to a function and have the function behave differently depending on whether it's a "list" style array or a "hash" style array. E.g.:

myfunc(array("One", "Two", "Three")); // works
myfunc(array(1=>"One", 2=>"Two", 3=>"Three")); also works, but understands it's a hash

Might output something like:

One, Two, Three
1=One, 2=Two, 3=Three

ie: the function does something differently when it "detects" it's being passed a hash rather than an array. Can you tell I'm coming from a Perl background where %hashes are different references from @arrays?

I believe my example is significant because we can't just test to see whether the key is numeric, because you could very well be using numeric keys in your hash.

I'm specifically looking to avoid having to use the messier construct of myfunc(array(array(1=>"One"), array(2=>"Two"), array(3=>"Three")))

5
  • 2
    Looking for something like this? stackoverflow.com/questions/173400/… Commented May 13, 2011 at 19:30
  • Interesting question. PHP unfortunately does not distinguish array('a','b','c') from array(0=>'a',1=>'b',2=>'c')... Commented May 13, 2011 at 19:31
  • 1
    PHP will always store numeric keys array("1" => "1") as integers. Can't detect that. You can only probe for continually growing keys to differentiate true lists from indexed arrays. Commented May 13, 2011 at 19:35
  • Correct. Unfortunately it does not distinguish 'simple' arrays and those with numeric strings as keys (see my answer). Commented May 13, 2011 at 19:42
  • Duplicates stackoverflow.com/q/173400/287948 Commented Sep 5, 2015 at 19:41

6 Answers 6

39

Pulled right out of the kohana framework.

public static function is_assoc(array $array)
{
    // Keys of the array
    $keys = array_keys($array);

    // If the array keys of the keys match the keys, then the array must
    // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
    return array_keys($keys) !== $keys;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Very nice indeed. I'm still taking this a little bit on faith.... but it works in my code, so thanks!
This fails for non-associative arrays that have been filtered, like [5 => 'foo'].
array_keys($arr) !== range(0, count($arr) - 1) as stackoverflow.com/a/173479/287948 ,
[0 => 'a', 2 => 'b', 3=>'c'] returns true with this method
also used by Laravel framework. (in Illuminate\Support\Arr class)
17

This benchmark gives 3 methods.

Here's a summary, sorted from fastest to slowest. For more informations, read the complete benchmark here.

1. Using array_values()

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

2. Using array_keys()

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

3. Using array_filter()

function($array){
    return count(array_filter(array_keys($array), 'is_string')) > 0;
}

2 Comments

It would be good to annotate this with the actual benchmark results in case the link goes stale. It's very important to note that the array_filter approach is orders of magnitude slower than the others.
[0 => 'a', 2 => 'b', 3=>'c'] returns true for the first case.
7

PHP treats all arrays as hashes, technically, so there is not an exact way to do this. Your best bet would be the following I believe:

if (array_keys($array) === range(0, count($array) - 1)) {
   //it is a hash
}

1 Comment

This is the only solution that was posted so I'll accept it, despite its inherent limitation of not accounting for non-sequential array indices. This question: stackoverflow.com/questions/173400/… has a lot more solutions, none of which are perfect.
0

No, PHP does not differentiate arrays where the keys are numeric strings from the arrays where the keys are integers in cases like the following:

$a = array("0"=>'a', "1"=>'b', "2"=>'c');
$b = array(0=>'a', 1=>'b', 2=>'c');

var_dump(array_keys($a), array_keys($b));

It outputs:

array(3) {
    [0]=> int(0) [1]=> int(1) [2]=> int(2)
}
array(3) {
    [0]=> int(0) [1]=> int(1) [2]=> int(2)
}

(above formatted for readability)

Comments

0

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.

3 Comments

Walk me through your function, because there's something I'm missing. You have a foreach loop, but you return on the very first iteration of that loop! What's the point of the loop if you're just returning whether the first key is an integer? Perhaps you meant to set a flag to true and then in our loop if it IS an integer, set the flag to false?
otherwise you always get an integer value as an array key thats why!
This is closer to the real answer IMHO. Tom Auger has a point, though. I think it would be more like: private function is_hash($array) { foreach(array_keys($array) as $key) { if (is_int($key)) { return false; } } return true; }
0

Being a little frustrated, trying to write a function to address all combinations, an idea clicked in my mind: parse json_encode result.

When a json string contains a curly brace, then it must contain an object!

Of course, after reading the solutions here, mine is a bit funny... Anyway, I want to share it with the community, just to present an attempt to solve the problem from another prospective (more "visual").


function isAssociative(array $arr): bool
    {
        // consider empty, and [0, 1, 2, ...] sequential
        if(empty($arr) || array_is_list($arr)) {
            return false;
        }

        // first scenario:
        // [  1  => [*any*] ]
        // [ 'a' => [*any*] ]
        foreach ($arr as $key => $value) {
            if(is_array($value)) {
                return true;
            }
        }

         // second scenario: read the json string
        $jsonNest = json_encode($arr, JSON_THROW_ON_ERROR);

        return str_contains($jsonNest, '{'); // {} assoc, [] sequential
    }

NOTES

[email protected] is required, check out the gist on github containing the unit test of this method + Polyfills (php>=7.3).

I've tested also Hussard's posted solutions, A & B are passing all tests, C fails to recognize: {"1":0,"2":1}.

BENCHMARKS

Here json parsing is ~200 ms behind B, but still 1.7 seconds faster than solution C!

What do you think about this version? Improvements are welcome!

8 Comments

empty($arr) can be safely replaced with the function-less equivalent !$arr.
does it cover empty string, and variable without a value cases?
empty($v) and !$v will both return true on a string with no length. They both perform a loosely typed false check.
Interesting, thank you! I thought that empty() was the most safe way to do this kind of check.
!empty() or isset() should ONLY be used if you need to check that the variable exists AND you want the secondary check that they provide. !empty() checks that the variable exists and is not "falsey". isset() checks that the variable exists and is not null. There are many pages on Stack Overflow that explain the intricacies in excruciating detail.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.