Wilco
Wilco

Reputation: 33356

How to check if PHP 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 "is a list" (contains only numeric keys starting from 0)?

Basically, I want to be able to differentiate between this:

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

and this:

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

Upvotes: 934

Views: 351980

Answers (30)

Loading
Loading

Reputation: 109

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

// Too short :)
function is_assoc(&$arr) {
    return json_encode($arr)[0] === '{';
}

Upvotes: 10

Muhammad Dyas Yaskur
Muhammad Dyas Yaskur

Reputation: 8098

PHP 8.1 adds a built-in function to determine whether an array is a list with those semantics, or not. the function is array_is_list:

$list = ["a", "b", "c"];

array_is_list($list); // true

$notAList = [1 => "a", 2 => "b", 3 => "c"];

array_is_list($notAList); // false

$alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"];

array_is_list($alsoNotAList); // false

Please note that this function returns true on empty arrays.

array_is_list([]); // true

reference

Upvotes: 61

Greg
Greg

Reputation: 321638

Since 8.1 PHP has a simple answer, array_is_list().

For the legacy code you can use the following function (wrapping it in function_exists() to make it portable):

if (!function_exists('array_is_list')) {
    function array_is_list(array $arr)
    {
        if ($arr === []) {
            return true;
        }
        return array_keys($arr) === range(0, count($arr) - 1);
    }
}

And then you can use the this function with any PHP version.

var_dump(array_is_list([])); // true
var_dump(array_is_list(['a', 'b', 'c'])); // true
var_dump(array_is_list(["0" => 'a', "1" => 'b', "2" => 'c'])); // true
var_dump(array_is_list(["1" => 'a', "0" => 'b', "2" => 'c'])); // false
var_dump(array_is_list(["a" => 'a', "b" => 'b', "c" => 'c'])); // false

Upvotes: 811

Prathamesh Datar
Prathamesh Datar

Reputation: 405

Check if the data type is array, then json encode then decode the data and check if the new data type is object.

public function is_assoc_array($array)
{
    return is_array($array)
           && is_object(json_decode(json_encode($array)));
}

Some examples

echo is_assoc_array(['one', 'two', 'three']) ? 'Yes' : 'No'); \\No
    
echo is_assoc_array(['one' => 'one', 'two' => 'two', 'three' => 'three']) ? 'Yes' : 'No'; \\Yes
    
echo is_assoc_array(['1' => 'one', '2' => 'two', '3' => 'three']) ? 'Yes' : 'No'; \\Yes
    
echo is_assoc_array(['0' => 'one', '1' => 'two', '2' => 'three']) ? 'Yes' : 'No'; \\No

There was a similar solution by @devios1 in one of the answers but this was just another way using the inbuilt json related functions of PHP. I haven't checked how this solution fairs in terms of performance compared to other solutions that have been posted here.

Upvotes: -2

godzillante
godzillante

Reputation: 1214

Just adding my two cents, and I think it should be pretty efficient based on @nonsensei 's benchmarks as well as being clearly legible:

function is_associative(array $array): bool
{
    foreach ($array as $key => $value)
    {
        if (!is_string($key)) return false;
    }
    return true;
}

Upvotes: 1

PaulH
PaulH

Reputation: 3049

For those using Laravel:

Arr::isAssoc returns true if the given array is an associative array. An array is considered "associative" if it doesn't have sequential numerical keys beginning with zero:

use Illuminate\Support\Arr;

$isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]);

// true

$isAssoc = Arr::isAssoc([1, 2, 3]);

// false

https://laravel.com/docs/8.x/helpers#method-array-isassoc

Upvotes: 4

Reputation:

Actually the most efficient way is thus:

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

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

Laravel use this approach.

Upvotes: 31

Manu Manjunath
Manu Manjunath

Reputation: 6411

Most answers have sub-optimal time/space complexity or are changing semantics. So, here is another answer with the fastest and most functionally correct solution:

function is_sequential_array(Array &$a) {
    $n = count($a);
    for($i=0; $i<$n; $i++) {
        if(!array_key_exists($i, $a)) {
            return false;
        }
    }
    return true;
}

This answer has following advantages over other answers:

  1. Space complexity of O(1) (many answers here use O(n) space!)
  2. Does not apply equality on keys (which is an unwanted and expensive operation)
  3. Treats input array as immutable (many answers have implicitly created a copy by applying mutating functions)
  4. Uses function array_key_exists instead of isset (remember, isset additionally checks for 'is not null', thereby changing semantics)
  5. Worst-case time complexity is O(n) (many answers here have best-case time complexity of O(n))

Upvotes: 6

dipenparmar12
dipenparmar12

Reputation: 3455

Here is another simple but powerful logic ( which is also used by great Laravel framework in it's internal mechanism )

/**
 * Determines if an array is associative.
 * @param  array  $array
 * @return bool
 */
function isAssoc(array $array)
{
    $keys = array_keys($array);

    return array_keys($keys) !== $keys;
}

Upvotes: 16

pilat
pilat

Reputation: 1162

Sometimes you can get away with only checking if the first array's Key is 0.

$isSequential = array_keys($arr)[0] === 0

, or, faster, but more verbose version:

reset($arr); $isSequential = key($arr) === 0

Upvotes: 1

Aylian Craspa
Aylian Craspa

Reputation: 466

This question is actually useless when it comes to array of php because with the nature of php an array should not have to be fully associative or indexing, it can be combination of both, the way user have define and assigned the value an array can be a combination of both. see the example below

$y= array(5);
$y["0x"]="n";
$y["vbg"]="12132";
$y[1] = "k";

var_dump($y); //this will output 4 element array

echo "</br>" .$y["0x"]."</br>".$y[0];

for($x=0;$x<sizeof($y);$x++){ // this will output all index elements & gives error after that
    echo "</br> index elements ".$y[$x];
}

so the correct question that has to ask is , is all the element in array are associative or index. if you really know that it will only be either associative or indexing not a combination of these two, you can just simply use this method to find wether it is an index or associative array.

function AssocTest(&$arr){
    if(is_array($arr)){

        reset($arr); // reset pointer to first element of array

        if(gettype(key($arr)) == "string"){ //get the type(nature) of first element key 
            return true;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

you can use it as normal function

echo(AssocTest($y)?  "Associative array": "Not an Associative array/ Not an array at all");

and important thing to remember evan you have initialize an array as associative but the names that you have gave the associative array is just numbers it will treat as an index array when it being read by php if you have not explicitly gave the string names. take a look at the example below.

$y["0"]="n";
$y["1"]="12132";
$y["22"] = "k";

//both will get the same output
echo "<br/> s0 ".$y["22"];
echo "<br/> s0 ".$y[22];

for($x=0;$x<count($y);$x++){
   echo "<br/> arr ".$y[$x]; // this will output up to 2nd element and give an error after

}

so if you need to be sure all the elements of the array to be exactly indexed or either associative , there is no other way but go true all the elements and check each and every element key by generated index array as post by many people here.

function fullAssocTest(&$arr)
{
    if(is_array($arr)){
        return (array_keys($arr) !== range(0, count($arr) - 1));
    }
}

its less coding, but this thing is really process intensive and really un-necessary work.

Upvotes: 1

geilt
geilt

Reputation: 805

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

You run a single count on the main array and store a single integer. You then loop through the array and check for an exact match while iterating the counter. You should have from 1 to count. If it fails it will short circuit out which gives you performance boost when it is false.

Originally I did this with a for loop and checking for isset( $arr[$i] ) but this will not detect null keys which requires array_key_exists, and as we know that is the worst function to use for speed.

Constantly updating variables via foreach to check along with the iterator never growing past it's integer size let's PHP use it's built in memory optimization, caching and garbage collection to keep you at very low resource usage.

Also, I will argue that using array_keys in a foreach is silly when you can simply run $key => $value and check the key. Why create the new data point? Once you abstract away the array keys you've consumed more memory immediately.

Upvotes: 3

squirrel
squirrel

Reputation: 2010

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

function keyedNext( &$arr, &$k){
    $k = key($arr);
    return next($arr);
}

for ($k = key(reset($my_array)); is_int($k); keyedNext($my_array,$k))
    $onlyIntKeys = is_null($k);

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

Upvotes: 79

TylerY86
TylerY86

Reputation: 3792

After some local benchmarking, debugging, compiler probing, profiling, and abusing 3v4l.org to benchmark across more versions (yes, I got a warning to stop) and comparing against every variation I could find...

I give you an organically derived best-average-worst-case scenario associative array test function that is at worst roughly as good as or better than all other average-case scenarios.

/**
 * 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;
}

From 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";
}

Upvotes: 4

Slayer Birden
Slayer Birden

Reputation: 3694

I've come up with next method:

function isSequential(array $list): bool
{
    $i = 0;
    $count = count($list);
    while (array_key_exists($i, $list)) {
        $i += 1;
        if ($i === $count) {
            return true;
        }
    }

    return false;
}


var_dump(isSequential(array())); // false
var_dump(isSequential(array('a', 'b', 'c'))); // true
var_dump(isSequential(array("0" => 'a', "1" => 'b', "2" => 'c'))); // true
var_dump(isSequential(array("1" => 'a', "0" => 'b', "2" => 'c'))); // true
var_dump(isSequential(array("1a" => 'a', "0b" => 'b', "2c" => 'c'))); // false
var_dump(isSequential(array("a" => 'a', "b" => 'b', "c" => 'c'))); // false

*Note empty array is not considered a sequential array, but I think it's fine since empty arrays is like 0 - doesn't matter it's plus or minus, it's empty.

Here are the advantages of this method compared to some listed above:

  • It does not involve copying of arrays (someone mentioned in this gist https://gist.github.com/Thinkscape/1965669 that array_values does not involve copying - what!?? It certainly does - as will be seen below)
  • It's faster for bigger arrays and more memory friendly at the same time

I've used benchmark kindly provided by Artur Bodera, where I changed one of the arrays to 1M elements (array_fill(0, 1000000, uniqid()), // big numeric array).

Here are the results for 100 iterations:

PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )

Initial memory: 32.42 MB
Testing my_method (isset check) - 100 iterations
  Total time: 2.57942 s
  Total memory: 32.48 MB

Testing method3 (array_filter of keys) - 100 iterations
  Total time: 5.10964 s
  Total memory: 64.42 MB

Testing method1 (array_values check) - 100 iterations
  Total time: 3.07591 s
  Total memory: 64.42 MB

Testing method2 (array_keys comparison) - 100 iterations
  Total time: 5.62937 s
  Total memory: 96.43 MB

*Methods are ordered based on their memory consumption

**I used echo " Total memory: " . number_format(memory_get_peak_usage()/1024/1024, 2) . " MB\n"; to display memory usage

Upvotes: 0

Rbgo Web
Rbgo Web

Reputation: 146

/*
iszba - Is Zero Based Array

Detects if an array is zero based or not.

PARAMS:
    $chkvfnc
        Callback in the loop allows to check the values of each element.
        Signature:
            bool function chkvfnc($v);
            return:
                true    continue looping
                false   stop looping; iszba returns false too.

NOTES:
○ assert: $array is an array.
○ May be memory efficient;
  it doesn't get extra arrays via array_keys() or ranges() into the function.
○ Is pretty fast without a callback.
○ With callback it's ~2.4 times slower.
*/
function iszba($array, $chkvfnc=null){

    $ncb = !$chkvfnc;
    $i = 0;

    foreach($array as $k => $v){
        if($k === $i++)
            if($ncb || $chkvfnc($v))
                continue;

        return false;
    }

    return true;
}

• Without callback it is ~30% faster than current leading answer, and possibly more memory efficient.

• Just negate the answer to know if the array should be considered associative.

Upvotes: -1

Ben
Ben

Reputation: 3060

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

Upvotes: 7

Minwork
Minwork

Reputation: 1022

Or you can just use this:

Arr::isAssoc($array)

which will check if array contains any non-numeric key or:

Arr:isAssoc($array, true)

to check if array is strictly sequencial (contains auto generated int keys 0 to n-1)

using this library.

Upvotes: 1

Justin Levene
Justin Levene

Reputation: 1677

Improvement from Mark Amery

function isAssoc($arr)
{
    // Is it set, is an array, not empty and keys are not sequentialy numeric from 0
    return isset($arr) && is_array($arr) && count($arr)!=0 && array_keys($arr) !== range(0, count($arr) - 1);
}

This tests if variable exists, if it is an array, if it is not an empty array and if the keys are not sequential from 0.

To see if the array is associative

if (isAssoc($array)) ...

To see if it numeric

if (!isAssoc($array)) ...

Upvotes: -1

nonsensei
nonsensei

Reputation: 480

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

results:

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

Upvotes: 3

Jesse
Jesse

Reputation: 6995

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.

Upvotes: 8

scronide
scronide

Reputation: 12238

function is_associative($arr) {
  return (array_merge($arr) !== $arr || count(array_filter($arr, 'is_string', ARRAY_FILTER_USE_KEY)) > 0);
}

Upvotes: 0

Byscripts
Byscripts

Reputation: 2588

My solution:

function isAssociative(array $array)
{
    return array_keys(array_merge($array)) !== range(0, count($array) - 1);
}

array_merge on a single array will reindex all integer keys, but not other. For example:

array_merge([1 => 'One', 3 => 'Three', 'two' => 'Two', 6 => 'Six']);

// This will returns [0 => 'One', 1 => 'Three', 'two' => 'Two', 2 => 'Six']

So if a list (a non-associative array) is created ['a', 'b', 'c'] then a value is removed unset($a[1]) then array_merge is called, the list is reindexed starting from 0.

Upvotes: 2

podperson
podperson

Reputation: 2383

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.

This is because 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:

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

Also note that if you don't care to differentiate sparse arrays from associative arrays you can simply return 'assoc' from both if blocks.

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.

Upvotes: 18

Captain kurO
Captain kurO

Reputation: 709

To merely check whether the array has non-integer keys (not whether the array is sequentially-indexed or zero-indexed):

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

If there is at least one string key, $array will be regarded as an associative array.

Upvotes: 489

c9s
c9s

Reputation: 1917

By using xarray PHP extension

You can do this very fast (about 30+ times faster in PHP 5.6):

if (array_is_indexed($array)) {  }

Or:

if (array_is_assoc($array)) {  }

Upvotes: 3

Kat Lim Ruiz
Kat Lim Ruiz

Reputation: 19

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.

Upvotes: 2

lazycommit
lazycommit

Reputation: 414

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

Upvotes: 2

Pang
Pang

Reputation: 10111

As stated by the OP:

PHP treats all arrays as associative

it is not quite sensible (IMHO) to write a function that checks if an array is associative. So first thing first: what is a key in a PHP array?:

The key can either be an integer or a string.

That means there are 3 possible cases:

  • Case 1. all keys are numeric / integers.
  • Case 2. all keys are strings.
  • Case 3. some keys are strings, some keys are numeric / integers.

We can check each case with the following functions.

Case 1: all keys are numeric / integers.

Note: This function returns true for empty arrays too.

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

Case 2: all keys are strings.

Note: This function returns true for empty arrays too.

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

Case 3. some keys are strings, some keys are numeric / integers.

Note: This function returns true for empty arrays too.

//! 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;
}

It follows that:


Now, for an array to be a "genuine" array that we are all accustomed to, meaning:

  • Its keys are all numeric / integers.
  • Its keys are sequential (i.e. increasing by step 1).
  • Its keys start from zero.

We can check with the following function.

Case 3a. keys are numeric / integers, sequential, and zero-based.

Note: This function returns true for empty arrays too.

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

Caveats / Pitfalls (or, even more peculiar facts about array keys in PHP)

Integer keys

The keys for these arrays are integers:

array(0 => "b");
array(13 => "b");
array(-13 => "b");          // Negative integers are also integers.
array(0x1A => "b");         // Hexadecimal notation.

String keys

The keys for these arrays are strings:

array("fish and chips" => "b");
array("" => "b");                                   // An empty string is also a string.
array("[email protected]" => "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.com/images/nav_logo114.png") => "b");   // Strings may even be binary!

Integer keys that look like strings

If you think the key in array("13" => "b") is a string, you are wrong. From the doc here:

Strings containing valid integers will be cast to the integer type. E.g. the key "8" will actually be stored under 8. On the other hand "08" will not be cast, as it isn't a valid decimal integer.

For example, the key for these arrays are integers:

array("13" => "b");
array("-13" => "b");                        // Negative, ok.

But the key for these arrays are strings:

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.

What's more, according to the doc,

The size of an integer is platform-dependent, although a maximum value of about two billion is the usual value (that's 32 bits signed). 64-bit platforms usually have a maximum value of about 9E18, except for Windows, which is always 32 bit. PHP does not support unsigned integers.

So the key for this array may or may not be an integer - it depends on your platform.

array("60000000000" => "b");                // Array key could be integer or string, it can fit into a 64-bit (but not 32-bit) integer.

Even worse, PHP tends to be buggy if the integer is near the 231 = 2,147,483,648 boundary (see bug 51430, bug 52899). For example, on my local environment (PHP 5.3.8 on XAMPP 1.7.7 on Windows 7), var_dump(array("2147483647" => "b")) gives

array(1) {
    [2147483647]=>
    string(1) "b"
}   

but on this live demo on codepad (PHP 5.2.5), the same expression gives

array(1) {
    ["2147483647"]=>
    string(1) "b"
}

So the key is an integer in one environment but a string in another, even though 2147483647 is a valid signed 32-bit integer.

Upvotes: 42

cloudfeet
cloudfeet

Reputation: 12863

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.

Upvotes: 3

Related Questions