How to check if PHP array is associative or sequential? - php

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

You have asked two questions that are not quite equivalent:
Firstly, how to determine whether an array has only numeric keys
Secondly, how to determine whether an array has sequential numeric keys, starting from 0
Consider which of these behaviours you actually need. (It may be that either will do for your purposes.)
The first question (simply checking that all keys are numeric) is answered well by Captain kurO.
For the second question (checking whether the array is zero-indexed and sequential), you can use the following function:
function isAssoc(array $arr)
{
if (array() === $arr) return false;
return array_keys($arr) !== range(0, count($arr) - 1);
}
var_dump(isAssoc(['a', 'b', 'c'])); // false
var_dump(isAssoc(["0" => 'a', "1" => 'b', "2" => 'c'])); // false
var_dump(isAssoc(["1" => 'a', "0" => 'b', "2" => 'c'])); // true
var_dump(isAssoc(["a" => 'a', "b" => 'b', "c" => 'c'])); // true

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.

Surely this is a better alternative.
<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;

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

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:
If the value is not an array, all 3 functions return false.
If the value is an empty array, all 3 functions return true
(which is by definition, as in "the empty set is a subset of any set A because all its elements belong to A").
If the value is a non-empty array, exactly 1 function returns true.
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("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.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.

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
reference

Speed-wise:
function 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:
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.

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

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.

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

I think the following two functions are the best way to go for checking 'if an array is associative or numeric'. Since 'numeric' could mean only numeric keys or only sequential numeric keys, two functions are listed below that check either condition:
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));
}
The first function checks if each key is an integer value. The second function checks if each key is an integer value and in addition checks if all keys are sequential starting at $base, which defaults to 0 and thus can be omitted if you do not need to specify another base value. key($my_array) returns null if the read pointer is moved past the end of the array, which is what ends the for loop and makes the statement after the for loop return true if all keys were integer. If not, the loop ends prematurely because a key is of type string, and the statement after the for loop will return false. The latter function in addition adds one to $base after each compare, to be able to check if the next key is of the correct value. The strict compare makes it also check if the key is of type integer. The $base = (int) $base part in the first section of the for loop can be left out when $base is omitted or if you make sure it is only called using an integer. But since I can't be sure for everybody, I left it in. The statement is executed only once, anyway. I think these are the most efficient solutions:
Memory wise: No copying of data or key ranges. Doing an array_values or array_keys may seem shorter (less code) but keep in mind what goes on in the background once you make that call. Yes there are more (visible) statements than in some other solutions, but that is not what counts, is it?
Time wise: Besides the fact that copying/extracting data and/or keys also takes time, this solution is more efficient than doing a foreach. Again a foreach may seem more efficient to some because it is shorter in notation, but in the background foreach also calls reset, key and next to do it's looping. But in addition it also calls valid to check the end condition, which is avoided here due to the combination with the integer check.
Remember that an array key can only be an integer or a string, and a strictly numeric string such as "1" (but not "01") will be translated into an integer. Which is what makes checking for an integer key the only needed operation besides counting if you want the array to be sequential. Naturally, if is_indexed_array returns false the array can be seen as associative. I say 'seen', because in fact they all are.

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) {
ksort($arr);
return json_encode($arr)[0] === '{';
}

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.

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

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

I noticed two popular approaches for this question: one using array_values() and other using key(). To find out which is faster, I wrote a small program:
$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";
Output for the program on PHP 5.2 on CentOS is as follows:
Time taken with method #1 = 10.745ms
Time taken with method #2 = 18.239ms
Output on PHP 5.3 yielded similar results. Obviously using array_values() is much faster.

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

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

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.

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)) { }

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

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.

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

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

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

This would work too (demo):
function array_has_numeric_keys_only(array $array)
{
try {
SplFixedArray::fromArray($array, true);
} catch (InvalidArgumentException $e) {
return false;
}
return true;
}
Please note that the main point of this answer is to inform you about the existence of SplFixedArray and not to encourage you to use Exceptions for these kinds of tests.

I think the definition of a scalar array will vary by application. That is, some applications will require a more strict sense of what qualifies as a scalar array, and some applications will require a more loose sense.
Below I present 3 methods of varying strictness.
<?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;
}

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

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.

Related

Test if array of the form [x_1, x_2, x_3 ...] [duplicate]

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'
];
You have asked two questions that are not quite equivalent:
Firstly, how to determine whether an array has only numeric keys
Secondly, how to determine whether an array has sequential numeric keys, starting from 0
Consider which of these behaviours you actually need. (It may be that either will do for your purposes.)
The first question (simply checking that all keys are numeric) is answered well by Captain kurO.
For the second question (checking whether the array is zero-indexed and sequential), you can use the following function:
function isAssoc(array $arr)
{
if (array() === $arr) return false;
return array_keys($arr) !== range(0, count($arr) - 1);
}
var_dump(isAssoc(['a', 'b', 'c'])); // false
var_dump(isAssoc(["0" => 'a', "1" => 'b', "2" => 'c'])); // false
var_dump(isAssoc(["1" => 'a', "0" => 'b', "2" => 'c'])); // true
var_dump(isAssoc(["a" => 'a', "b" => 'b', "c" => 'c'])); // true
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.
Surely this is a better alternative.
<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;
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 "").
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:
If the value is not an array, all 3 functions return false.
If the value is an empty array, all 3 functions return true
(which is by definition, as in "the empty set is a subset of any set A because all its elements belong to A").
If the value is a non-empty array, exactly 1 function returns true.
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("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.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.
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
reference
Speed-wise:
function 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:
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.
function checkAssoc($array){
return ctype_digit( implode('', array_keys($array) ) );
}
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.
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;
}
I think the following two functions are the best way to go for checking 'if an array is associative or numeric'. Since 'numeric' could mean only numeric keys or only sequential numeric keys, two functions are listed below that check either condition:
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));
}
The first function checks if each key is an integer value. The second function checks if each key is an integer value and in addition checks if all keys are sequential starting at $base, which defaults to 0 and thus can be omitted if you do not need to specify another base value. key($my_array) returns null if the read pointer is moved past the end of the array, which is what ends the for loop and makes the statement after the for loop return true if all keys were integer. If not, the loop ends prematurely because a key is of type string, and the statement after the for loop will return false. The latter function in addition adds one to $base after each compare, to be able to check if the next key is of the correct value. The strict compare makes it also check if the key is of type integer. The $base = (int) $base part in the first section of the for loop can be left out when $base is omitted or if you make sure it is only called using an integer. But since I can't be sure for everybody, I left it in. The statement is executed only once, anyway. I think these are the most efficient solutions:
Memory wise: No copying of data or key ranges. Doing an array_values or array_keys may seem shorter (less code) but keep in mind what goes on in the background once you make that call. Yes there are more (visible) statements than in some other solutions, but that is not what counts, is it?
Time wise: Besides the fact that copying/extracting data and/or keys also takes time, this solution is more efficient than doing a foreach. Again a foreach may seem more efficient to some because it is shorter in notation, but in the background foreach also calls reset, key and next to do it's looping. But in addition it also calls valid to check the end condition, which is avoided here due to the combination with the integer check.
Remember that an array key can only be an integer or a string, and a strictly numeric string such as "1" (but not "01") will be translated into an integer. Which is what makes checking for an integer key the only needed operation besides counting if you want the array to be sequential. Naturally, if is_indexed_array returns false the array can be seen as associative. I say 'seen', because in fact they all are.
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) {
ksort($arr);
return json_encode($arr)[0] === '{';
}
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.
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;
}
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
I noticed two popular approaches for this question: one using array_values() and other using key(). To find out which is faster, I wrote a small program:
$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";
Output for the program on PHP 5.2 on CentOS is as follows:
Time taken with method #1 = 10.745ms
Time taken with method #2 = 18.239ms
Output on PHP 5.3 yielded similar results. Obviously using array_values() is much faster.
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:
Space complexity of O(1) (many answers here use O(n) space!)
Does not apply equality on keys (which is an unwanted and expensive operation)
Treats input array as immutable (many answers have implicitly created a copy by applying mutating functions)
Uses function array_key_exists instead of isset (remember, isset additionally checks for 'is not null', thereby changing semantics)
Worst-case time complexity is O(n) (many answers here have best-case time complexity of O(n))
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";
}
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.
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)) { }
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.
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
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
<?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)
This would work too (demo):
function array_has_numeric_keys_only(array $array)
{
try {
SplFixedArray::fromArray($array, true);
} catch (InvalidArgumentException $e) {
return false;
}
return true;
}
Please note that the main point of this answer is to inform you about the existence of SplFixedArray and not to encourage you to use Exceptions for these kinds of tests.
I think the definition of a scalar array will vary by application. That is, some applications will require a more strict sense of what qualifies as a scalar array, and some applications will require a more loose sense.
Below I present 3 methods of varying strictness.
<?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;
}
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__)
);
}
}
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.
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.

PHP - Elegantly extract the numeric indices in array a that are not in array b (not array_diff_key)

Suppose you have two arrays $a=array('apple','banana','canaple'); and $b=array('apple');, how do you (elegantly) extract the numeric indices of elements in array a that aren't in array b? (in this case, indices: 1 and 2).
In this case, array a will always have more elements than b.
Note, this is not asking for array_diff_key, but rather the numeric indices in the array with more elements that don't exist in the array with fewer elements.
array_diff gets you half way there. Using array_keys on the diff gets you the rest of what you want.
$a = ['apple','banana','canaple'];
$b = ['apple'];
$diff = array_diff($a, $b);
$keys = array_keys($diff);
var_dump($keys); // [1, 2]
This is because array_diff returns both the element and it's key from the first array. If you wanted to write a PHP implementation of array_diff it might look something like this...
function array_diff(Array ... $arrays) {
$return = [];
$cmp = array_shift($arrays);
foreach ($cmp as $key => $value) {
foreach($arrays as $array) {
if (!in_array($value, $array)) {
$return[$key] = $value;
}
}
}
return $return;
}
This gives you an idea how you might achieve the result, but internally php implements this as a sort, because it's much faster than the aforementioned implementation.

How to remove values from an array if occurring more than one time?

I need to remove values from an array that occur more than one time in the array.
For example:
$value = array(10,10,5,8);
I need this result:
$value = array(5,8);
Is there any in-built function in php?
I tried this, but this will not return my expected result:
$unique = array_unique($value);
$dupes = array_diff_key($value, $unique);
You can use array functions and ditch the foreach loops if you wish:
Here is a one-liner:
Code:
$value = [10, 10, 5, 8];
var_export(array_keys(array_intersect(array_count_values($value),[1])));
As multi-line:
var_export(
array_keys(
array_intersect(
array_count_values($value),
[1]
)
)
);
Output:
array (
0 => 5,
1 => 8,
)
This gets the value counts as an array, then uses array_intersect() to only retain values that occur once, then turns the keys into the values of a zero-index array.
The above snippet works identically to #modsfabio's and #axiac's answers. The ONLY advantage in my snippet is brevity. It is possible that their solutions may outperform mine, but judging speed on relatively small data sets may be a waste of dev time. For anyone processing relatively large data sets, do your own benchmarking to find the technique that works best.
For lowest computational/time complexity, use a single loop and as you iterate conditionally populate a lookup array and unset() as needed.
Code: (Demo) (Crosslink to my CodeReview answer)
$values = [10, 10, 5, 8];
$found = [];
foreach ($values as $index => $value) {
if (!isset($found[$value])) {
$found[$value] = $index;
} else {
unset($values[$index], $values[$found[$value]]);
}
}
var_export($values);
// [2 => 5, 3 => 8]
A couple of notes:
If processing float values, using a technique that stores the values as keys (as all of my snippets do), then the results may be incorrect because php will change floats to integers when used as keys.
PHP is consistently much faster at searching for keys than it is at searching for values.
You can do it like this using array_count_values() and a foreach loop:
<?php
$input = array(10,10,5,8);
$output = array();
foreach(array_count_values($input) as $value => $count)
{
if($count == 1)
{
$output[] = $value;
}
}
var_dump($output);
Output:
array(2) {
[0]=>
int(5)
[1]=>
int(8)
}
Example: https://eval.in/819461
A possible approach:
$value = array(10,10,5,8);
$output = array_keys(
array_filter(
array_count_values($value),
function ($count) {
return $count == 1;
}
)
)
array_count_values() produces an array that associates to each unique value from $value the number of times it appears in the array.
array_filter() keeps in this result only the entries (the keys) that appear only once in the original array.
array_keys() produces the desired result.
I would use array_count_values to get an array with how often every element occurs in the array. Then remove all the elements from the original array that occur more than once.
You need to use array_count_values(), array_search() and unset() functions.
<?php
$value = array(10,10,5,8);
echo '<pre>';print_r($value);echo '</pre>';
$cnt = array_count_values($value);
$dup = array();
foreach ($cnt as $k => $repeated) {
if ($repeated > 1) {
if(($key = array_search($k, $value)) !== false) {
unset($value[$key]);
}
}
}
echo '<pre>';print_r($cnt);echo '</pre>';
echo '<pre>';print_r($value);echo '</pre>';
?>
Demo
you can use
foreach loop
and
array_diff() function:
$value=array(10,10,5,8);
$duplicated=array();
foreach($value as $k=>$v)
{
if($kt=array_search($v,$value))!==false and
$k!=$kt)
{if (count(array_keys($array, $value)) > 1)
{
/* Execute code */
}
unset($value[$kt];$duplicated[]=$v;
}
}
$result=array_diff($values,$duplicated);
print_r($result);
output
Array([2]=>5[3]=>8)

PHP array intersect for only one occurrence of value

I'm trying to create a function to find the HCF of two values. I currently have a function that finds all the prime factors of each value and returns them in an array. To find the HCF, all that has to be done would be to compare the similar values in each array then multiply them together.
My code currently looks like this:
function hcf($x, $y) {
$hcf = array_product(array_intersect(prm_fac($x), prm_fac($y)));
if ($hcf != 0)
return $hcf;
else
return 1;
It's hard to explain, so I will show an example of the problem: If I try and find the HCF of 10 and 8, the prime factors of 10 will be 2, 5; the prime factors of 8 will be 2, 2, 2. The similar values in both arrays will be 2.
However, when I use the array_intersect function, it takes all the occurrences of 2, instead of just the single occurrence where it intersects. So instead of getting 2, I will get 2, 2, 2. How can I fix this problem?
Here is another example: I need to find the HCF of 4 and 16. The prime factors of 4 are 2, 2; the prime factors of 16 are 2, 2, 2, 2. I need to find the which values are the same for both arrays. If I use array_intersect on both arrays, it will give me 2, 2, 2, 2 instead of 2, 2. How do I fix this?
Here is the prm_fac function:
function prm_fac($n) {
$factors = array();
while ($n % 2 == 0) {
$factors[] = 2;
$n /= 2;
}
for ($i = 3; $i <= sqrt($n); $i += 2) {
while ($n % $i == 0) {
$factors[] = $i;
$n /= $i;
}
}
if ($n != 1)
$factors[] = $n;
return $factors;
}
Instead of array_intersect you could use this custom function instead, which will take into account that values can repeat, but will only take them when they repeat as many times in both arrays.
The rest of your code can stay:
function common_values($a, $b) {
return array_filter($a, function($v) use (&$b) {
return ($i = array_search($v, $b)) !== false && ($b[$i] = -1);
});
}
So, call it like this:
function hcf($x, $y) {
$hcf = array_product(common_values(prm_fac($x), prm_fac($y)));
if ($hcf != 0)
return $hcf;
else
return 1;
}
Explanation of the function
array_filter($a, ...) iterates over every element of $a, and for each of them calls the function provided in the second argument. If that function returns a truthy value, the corresponding element will be included (and only then) in the array that is returned by array_filter.
That inner return value is calculated as follows:
($i = array_search($v, $b)) finds the index where the value ($v) from $a occurs in $b. This index is assigned to the variable $i (on-the-fly). Then its value is compared with false, which tells us whether there was a match or not. If not, the rest of the expression is not evaluated because the && can never make the total expression true again. And so the return value is false, i.e. this value from $a is excluded (because it does not occur in $b).
In the other case, $i will not be false but an integer index, so the first comparison is true. Then the next part of the && is evaluated:
($b[$i] = -1)
The matching value in $b is wiped out so to make sure it cannot match again in any next iteration. It is wiped out with a negative value, as factors are expected to be always positive, and non-zero values also are truthy so that the return value of array_filter is true, i.e. this value from $a must be included in the result.
Notes and refereces
Note that HCF is also known as GCD. See also this solution to get it in a more direct way, or use gmp-gcd from the GMP extension.
You can use array_unique() to remove duplicates from the result array returned from array_intersect().
I think it would be better if you remove duplicates from the prm_fac() array. Something like :
$hcf = array_product(array_intersect(array_unique(prm_fac($x)), array_unique(prm_fac($y))));
Best practice would be to write it in you prm_fac function itself -
function prm_fac($val) {
.
.
.
return array_unique($factors);
}
We can make use of foreach to get the actual product array - This works for the egs I tried.
$product = array();
$array1 = prm_fac($x); //return the unique $x values
$array2 = prm_fac($y); //return the unique $y values
foreach ($array1 as $val1) {
foreach ($array2 as $val2) {
// Form the product array if the iterated values are present in the other array
if (in_array($val2, $array1) && in_array($val1, $array2)) {
$product[] = $val1;
$product[] = $val2;
}
}
}
Finally,
$hcf = array_product($product); //should give the proper product of values.

Rotate array elements to the left (move first element to last and re-index)

Is it possible to easily 'rotate' an array in PHP?
Like this:
1, 2, 3, 4 -> 2, 3 ,4 ,1
Is there some kind of built-in PHP function for this?
$numbers = array(1,2,3,4);
array_push($numbers, array_shift($numbers));
print_r($numbers);
Output
Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 1
)
Most of the current answers are correct, but only if you don't care about your indices:
$arr = array('foo' => 'bar', 'baz' => 'qux', 'wibble' => 'wobble');
array_push($arr, array_shift($arr));
print_r($arr);
Output:
Array
(
[baz] => qux
[wibble] => wobble
[0] => bar
)
To preserve your indices you can do something like:
$arr = array('foo' => 'bar', 'baz' => 'qux', 'wibble' => 'wobble');
$keys = array_keys($arr);
$val = $arr[$keys[0]];
unset($arr[$keys[0]]);
$arr[$keys[0]] = $val;
print_r($arr);
Output:
Array
(
[baz] => qux
[wibble] => wobble
[foo] => bar
)
Perhaps someone can do the rotation more succinctly than my four-line method, but this works anyway.
It's very simple and could be done in many ways. Example:
$array = array( 'a', 'b', 'c' );
$array[] = array_shift( $array );
Looping through the array, and shift-ing and push-ing, may be a common way to rotate an array, however it can often mess up your keys. A more robust method is using a combination of array_merge and array_splice.
/**
* Rotates an array.
*
* Numerical indexes will be renumbered automatically.
* Associations will be kept for keys which are strings.
*
* Rotations will always occur similar to shift and push,
* where the number of items denoted by the distance are
* removed from the start of the array and are appended.
*
* Negative distances work in reverse, and are similar to
* pop and unshift instead.
*
* Distance magnitudes greater than the length of the array
* can be interpreted as rotating an array more than a full
* rotation. This will be reduced to calculate the remaining
* rotation after all full rotations.
*
* #param array $array The original array to rotate.
* Passing a reference may cause the original array to be truncated.
* #param int $distance The number of elements to move to the end.
* Distance is automatically interpreted as an integer.
* #return array The modified array.
*/
function array_rotate($array, $distance = 1) {
settype($array, 'array');
$distance %= count($array);
return array_merge(
array_splice($array, $distance), // Last elements - moved to the start
$array // First elements - appended to the end
);
}
// Example rotating an array 180°.
$rotated_180 = array_rotate($array, count($array) / 2);
Alternatively, if you also find the need to rotate keys so that they match with different values, you can combine array_keys, array_combine, array_rotate, and array_values.
/**
* Rotates the keys of an array while keeping values in the same order.
*
* #see array_rotate(); for function arguments and output.
*/
function array_rotate_key($array, $distance = 1) {
$keys = array_keys((array)$array);
return array_combine(
array_rotate($keys, $distance), // Rotated keys
array_values((array)$array) // Values
);
}
Or alternatively rotating the values while keeping the keys in the same order (equivalent to calling the negative distance on the matching array_rotate_key function call).
/**
* Rotates the values of an array while keeping keys in the same order.
*
* #see array_rotate(); for function arguments and output.
*/
function array_rotate_value($array, $distance = 1) {
$values = array_values((array)$array);
return array_combine(
array_keys((array)$array), // Keys
array_rotate($values, $distance) // Rotated values
);
}
And finally, if you want to prevent renumbering of numerical indexes.
/**
* Rotates an array while keeping all key and value association.
*
* #see array_rotate(); for function arguments and output.
*/
function array_rotate_assoc($array, $distance = 1) {
$keys = array_keys((array)$array);
$values = array_values((array)$array);
return array_combine(
array_rotate($keys, $distance), // Rotated keys
array_rotate($values, $distance) // Rotated values
);
}
It could be beneficial to perform some benchmark tests, however, I expect that a small handful of rotations per request wouldn't affect performance noticeably regardless of which method is used.
It should also be possible to rotate an array by using a custom sorting function, but it would most likely be overly complicated. i.e. usort.
A method to maintain keys and rotate. using the same concept as array_push(array, array_shift(array)), instead we will use array_merge of 2 array_slices
$x = array("a" => 1, "b" => 2, "c" => 3, 'd' => 4);
To move the First element to the end
array_merge(array_slice($x, 1, NULL, true), array_slice($x, 0, 1, true)
//'b'=>2, 'c'=>3, 'd'=>4, 'a'=>1
To move the last element to the front
array_merge(array_slice($x, count($x) -1, 1, true), array_slice($x, 0,
//'d'=>4, 'a'=>1, 'b'=>2, 'c'=>3
you can use this function:
function arr_rotate(&$array,$rotate_count) {
for ($i = 0; $i < $rotate_count; $i++) {
array_push($array, array_shift($array));
}
}
usage:
$xarr = array('1','2','3','4','5');
arr_rotate($xarr, 2);
print_r($xarr);
result:
Array ( [0] => 3 [1] => 4 [2] => 5 [3] => 1 [4] => 2 )
There's a task about array rotation on Hackerrank: https://www.hackerrank.com/challenges/array-left-rotation/problem.
And proposed solution with array_push and array_shift will work for all test cases except the last one, which fails due to timeout. So, array_push and array_shift will give you not the fastest solution.
Here's the faster approach:
function leftRotation(array $array, $n) {
for ($i = 0; $i < $n; $i++) {
$value = array[$i]; unset(array[$i]); array[] = $value;
}
return array;
}
Use array_shift and array_push.
$daynamesArray = array("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday");
array_push($daynamesArray, array_shift($daynamesArray)); //shift by one
array_push($daynamesArray, array_shift($daynamesArray)); //shift by two
print_r($daynamesArray);
The output starts at "Wednesday":
Array ( [0] => Wednesday [1] => Thursday [2] => Friday [3] => Saturday [4] => Sunday [5] => Monday [6] => Tuesday
Yes it is, here is a function I did myself, where $A is the array and $K the number of times you want to rotate the array:
function solution($A, $K) {
for($i = 0; $i < $K; $i++): //we cycle $K
$arrayTemp = $A;
for($j = 0; $j < count($arrayTemp); $j++): // we cycle the array
if($j == count($arrayTemp) - 1) $A[0] = $arrayTemp[$j]; // we check for the last position
else $A[$j + 1] = $arrayTemp[$j]; // all but last position
endfor;
endfor;
return $A;
}
The logic is to swap the elements. Algorithm may look like -
for i = 0 to arrayLength - 1
swap( array[i], array[i+1] ) // Now array[i] has array[i+1] value and
// array[i+1] has array[i] value.
No. Check the documentation for array_shift and its related functions for some tools you can use to write one. There might even be an array_rotate function implemented in the comments of that page.
It's also worth reading through the array functions listed on the left-hand sidebar to get a full understanding of what array functions are available in PHP.
Here's a function to rotate an array (zero-indexed array) to any position you want:
function rotateArray($inputArray, $rotateIndex) {
if(isset($inputArray[$rotateIndex])) {
$startSlice = array_slice($inputArray, 0, $rotateIndex);
$endSlice = array_slice($inputArray, $rotateIndex);
return array_merge($endSlice, $startSlice);
}
return $inputArray;
}
$testArray = [1,2,3,4,5,6];
$testRotates = [3, 5, 0, 101, -5];
foreach($testRotates as $rotateIndex) {
print_r(rotateArray($testArray, $rotateIndex));
}
Not too dissimilar to the first snippet in ShaunCockerill's answer, I also endorse not making iterated functions calls to perform the rotation. In fact, I'll recommend using early returns to optimize performance and reduce the total number of function calls needed.
The following snippet is the "move left" version of the "move right" version that I posted here. In my demo, there is a single, static input array and the foreach() loop merely changes the desired amount of rotation (0 to 9).
Code: (Demo)
function shiftPop(array $indexedArray, int $shiftPopsCount): array
{
$count = count($indexedArray);
if ($count < 2) {
return $indexedArray;
}
$remainder = $shiftPopsCount % $count;
if (!$remainder) {
return $indexedArray;
}
return array_merge(
array_splice($indexedArray, $remainder),
$indexedArray
);
}
$array = [1, 2, 3, 4];
foreach (range(0, 9) as $moves) {
var_export(shiftPop($array, $moves));
echo "\n---\n";
}
The first if block in my snippet is not engaged by my demo because my array always has 4 elements. The second if block is engaged, when $moves is 0, 4, and 8 -- in these cases, the input is identical to the desired output, so calling array_merge() and array_splice() is pointless.

Categories