Is there any way to know if an array is associative or sequential?

Asked

Viewed 1,017 times

12

In PHP, a array can be both associative as it can be a list, with sequential numbers.

Is there any way to detect this difference in PHP?

Example:

$a = array('item 1', 'dois' => 'item 2'); // Associativo

$b = array('item 1', 'item 2'); // Sequencial
  • At the end you want the return for example, array x is numerical, or xis associative or x is mixed. I see zebras hahaha :D

  • @rray I ended up answering my question too. The array in PHP whether or not it’s a mess!

  • 3

    Probably just looking at the fountain. Pro PHP, apparently always associative: http://ideone.com/iEDhkz - I doubt very much that there is a flag indicating the difference internally (and I’m not very keen to look at the source for this).

  • Actually, I think I’m more into Splfixedarray

7 answers

7

A nice thing that can be done to know if the array is or is not associative is converting it to JSON and checking whether the first item of it equals the character [ instead of {.

When a array in PHP is sequential, the function json_encode generates a array in JSON format. When it is not sequential (even if all indexes are numerical)¹, it will generate a Object format JSON.

Behold:

$sequencial = array('bigown', 'bacco', 'gabe');
$nao_sequencial_1 = array(99 => 'bigown', 'bacco', 1 => 'gabe');
$nao_sequencial_2 = array('bigown' => 'moderador', 'bacco' => 'usuario', 'gabe' => 'cm');

When we call json_encode, the outputs are respectively:

["bigown","bacco","gabe"]

{"99":"bigown","100":"bacco","1":"gabe"}

{"bigown":"moderador","bacco":"usuario","gabe":"cm"}

Thus, the verification of the sequential array could be done thus:

function array_is_list(array $array) {
     return strpos(json_encode($array), "[") === 0;
}

Note ¹: Even if all the contents of a array are numerical, this does not make it sequential, since the numbers can be entered manually unorganized.

Note ²: If you set the values of the array sequentially manually, the evaluation of array_is_list would be positive.

For example, all cases below would return TRUE

array_is_list([1, 2, 3]);

array_is_list([0 => 1, 1 => 2, 2 => 3]);

array_is_list([0 => 1, 2, 3]);

Updating

PHP is about to release version 8.1. In this version, the function will be included array_is_list, in accordance with RFC (which was approved, by).

On this proposal, we have the following comments on the role

... Add a new Function array_is_list(array $array): bool that will Return true if the array Keys are 0 .. Count($array)-1 in that order. For other arrays, it Returns false. For non-arrays, it throws a Typeerror.

  • 1

    I tested with examples of simple arrays and the json_encode it was half the time that array_keys, but now I performed a test with a multidimensional array with more than 5000 items (I know it is very rare, only at the same test level) and it was 5 times slower, however in normal situations still json_encode seemed good. + 1

  • Function is_assoc($var) { Return is_array($var) and (array_values($var) !== $var); }

  • See my comment on the question

5


Considerations

In accordance with official documentation, one array may only have keys of the whole type or strings. The value can be of any type.

The key can be an integer or a string. The value can be of any type.

Some coercion that may occur:

  1. Strings containing valid integers, will be converted to the whole type. For example, the key "8" will, in fact, be stored as 8. Meanwhile, "08" will not be converted as it is not a valid decimal integer.

Therefore, the array ["8" => "foo"] should be considered as array of numerical and non-associative indices.

  1. Floats are also converted to integers; this means that the fractional part will be removed. For example, the key 8.7 will actually be stored as 8.

Therefore, the array [8.7 => "foo"] should be considered as array of numerical indexes also.

Floats will also be converted to integers including negative values. The key -5.6 will actually be stored as -5.

  1. Boolean are converted to integers, also; for example, the key true, will actually be stored as 1 and the key false as 0.

Therefore, the array [true => "foo", false => "bar] should be considered as array of numerical indexes also.

  1. Null will be converted to a string empty; for example, the key null will actually be stored as "".

Therefore, the array [null => "foo"] should be considered as array associative.

Other considerations:

  • Arrays mixed, such as ["foo", "key" => "bar"] shall be regarded as associations and numerical indices.

  • A array of numerical indices does not cease to be so when their indices are not sequential; therefore array [1 => "foo", 0 => "bar"] should also be considered as numerical indexes.

  • Similarly, a array with numeric indices that have one of their values removed (or that the sequence of the indices is not natural) will also remain numeric indices; therefore the array [0 => "foo", 1 => "bar", 6 => "baz"] shall be considered to be of numerical indices.

Contexts to be tested

Heed! For the remainder of the response, a array sequential said is, in fact, a array of numerical indices, but not necessarily of sequential indices.

That said, I have defined some tests that functions should be submitted:

/**
 * Lista de testes que a função deve ser submetida.
 * 
 * Cada item da lista deve ser um array com três índices.
 * O primeiro, "array", com o array a ser testado pela função.
 * O segundo, "is_sequential", um valor booleano esperado como retorno da função is_sequential.
 * O terceiro, "is_associative", um valor booleano esperado como retorno da função is_associative.
 */

$tests = array();

// Teste 1: Array com índices numéricos sequenciais

$tests[] = [
    "array" => ["a", "b", "c", "d", "e"], 
    "is_sequential" => true,
    "is_associative" => false
];

// Teste 2: Array associativo

$tests[] = [
    "array" => ["name" => "foo", "lastname" => "bar"], 
    "is_sequential" => false,
    "is_associative" => true
];

// Teste 3: Array com chave do tipo string contendo inteiro válido

$tests[] = [
    "array" => ["0" => "foo", "1" => "bar"], 
    "is_sequential" => true,
    "is_associative" => false
];

// Teste 4: Array com índices do tipo float

$tests[] = [
    "array" => [0.5 => "foo", -3.5 => "bar"], 
    "is_sequential" => true,
    "is_associative" => false
];

// Teste 5: Array com índices do tipo booleanos

$tests[] = [
    "array" => [true => "foo", false => "bar"], 
    "is_sequential" => true,
    "is_associative" => false
];

// Teste 6: Array com índice nulo

$tests[] = [
    "array" => [null => "foo"], 
    "is_sequential" => false,
    "is_associative" => true
];

// Teste 7: Array misto

$tests[] = [
    "array" => ["foo", "baz" => "bar"], 
    "is_sequential" => false,
    "is_associative" => false
];

// Teste 8: Array de índices numéricos desordenados

$tests[] = [
    "array" => [1 => "foo", 0 => "bar"], 
    "is_sequential" => true,
    "is_associative" => false
];

// Teste 9: Array de índices numéricos ordenados não sequenciais

$tests[] = [
    "array" => [0 => "foo", 1 => "bar", 6 => "baz"], 
    "is_sequential" => true,
    "is_associative" => false
];

Solution

The simplest solution I can see is to check the key type of the array, then:

/**
 * Função que testa se o array é sequencial.
 * 
 * @param array Array a ser testado
 * @return bool True se $array for sequencial, False caso contrário
 */

function is_sequential (array $array) {
    return array_filter($array, "is_int", ARRAY_FILTER_USE_KEY) == true;
}

/**
 * Função que testa se o array é associativo.
 * 
 * @param array Array a ser testado
 * @return bool True se $array for associativo, False caso contrário
 */

function is_associative (array $array) {
    return array_filter($array, "is_string", ARRAY_FILTER_USE_KEY) == true;
}

The above solutions use PHP’s native coercion in considering a array not empty as true and a array empty as false. Therefore, if any key/value of the array pass through the defined filter, the array resulting will be non-empty and the function will return true. If no key/value pair passes through the filter, the array result will be empty and the function return will be false.

Testing

The verification of the tests is done as follows:

try {
    foreach ($tests as $i => $test) {
        if ($test["is_sequential"] !== is_sequential($test["array"])) {
            throw new Exception(sprintf("is_sequential: Algo errado não está certo! Teste %d falhou.", $i+1));
        }

        if ($test["is_associative"] !== is_associative($test["array"])) {
            throw new Exception(sprintf("is_associative: Algo errado não está certo! Teste %d falhou.", $i+1));
        }
    }

    echo "Parabéns! Sua função passou em todos os testes.", PHP_EOL;
} catch (Exception $e) {
    echo $e->getMessage(), PHP_EOL;
}

Upshot

The result for the defined functions can be found in the Repl.it, or in the Ideone.

Inconsistencies

The solution presented above works according to the specifications of PHP itself; that is, if a array has at least one value that can be accessed via the numeric key, the array must be considered array of numerical indices; if the array have at least one value that can be accessed via type key string, the array must be considered array associative. Therefore, in the solution presented, a array mixed must return true to both functions. The inconsistency (semantics) that this solution generates is present when considering the following code snippet:

if (is_sequential($array)) {
    for ($i = 0; $i < count($array); $i++) {
        echo $array[$i], PHP_EOL;
    }
}

That is, if the array is numerically indexed, traverse all elements and display them on the screen. If the array is mixed, one of the elements will not be accessible in this way and at some point the index $i will not exist, resulting in error Undefined offset.

One way to circumvent this inconsistency is to add a new parameter to the functions named $strict, boolean type, that when true, the function is_sequential true return only when the array is strictly numerical and the function is_associative return true only when the array is strictly associative. In this way, the solution would be:

/**
 * Função que testa se o array é sequencial.
 * 
 * @param array Array a ser testado
 * @param bool Define se a verificação deve ser de modo rigoroso
 * @return bool True se $array for sequencial, False caso contrário
 */

function is_sequential (array $array, bool $strict = false) {
    return array_filter($array, "is_int", ARRAY_FILTER_USE_KEY) == ($strict ? $array : true);
}

/**
 * Função que testa se o array é associativo.
 * 
 * @param array Array a ser testado
 * @param bool Define se a verificação deve ser de modo rigoroso
 * @return bool True se $array for associativo, False caso contrário
 */

function is_associative (array $array, bool $strict = false) {
    return array_filter($array, "is_string", ARRAY_FILTER_USE_KEY) == ($strict ? $array : true);
}

In this way, the above-mentioned example would be:

// Array puramente de índices numéricos
$array = ["foo", "baz", "bar"];

// Passará pela condição e será exibido
if (is_sequential($array, true)) {
    for ($i = 0; $i < count($array); $i++) {
        echo $array[$i], PHP_EOL;
    }
}

// Array misto
$array = ["foo", "baz" => "bar"];

// Não passará pela condição e não será exibido
if (is_sequential($array, true)) {
    for ($i = 0; $i < count($array); $i++) {
        echo $array[$i], PHP_EOL;
    }
}

This example can be seen in Repl.it or in the Ideone.


Other solutions put forward

Note: The following checks of the other solutions presented here are for comparison only when considering all the situations foreseen by the documentation, through the above coercion. It is expected that they will fail some tests, as they do not take into account all the situations mentioned. This comparison should not be seen as a form of demerit on my part, demeaning the respective authors. I even believe faithfully that any kind of content here is taken advantage of in some way.

  1. The solution presented here, by Brunorb, would fail tests 4, 5, 7, 8 and 9. See tests here.

  2. The solution presented here, by William Nascimento, would fail tests 4, 5, 7, 8 and 9. See tests here.

  3. The solution presented here, by Wallace Maxters, would fail tests 4, 5, 7, 8 and 9. See tests here.

  4. The solution presented here (adapted), by Andrei Coelho, would fail tests 4, 7 and 9. See tests here.

  • Very good indeed. A note: in my first code only fails in item 7.

4

I suggest working only the keys, because this can be more performative than working the values (items), because these can be multidimensional arrays, so working only the keys can have a slight improvement.

Using array_keys

Another possible solution I found was suggested by Jesse Jan:

function is_seq($array) {
    $k = array_keys($array);
    return $k === array_keys($k);
}

function is_assoc($array) {
    return !is_seq($array);
}
  • The $k = array_keys($array); takes array keys, for example:

    • ['a' => 'foo', 'b' => 'bar']['a', 'b']
    • ['foo', 'bar'][0, 1]
    • ['a' => 2, 99 => 'bar']['a', 99]
  • The array_keys($k) "generates" an array with items that have the same amount of items as the original array, but in sequence starting from the 0

    • ['a' => 'foo', 'b' => 'bar']['a', 'b'][0, 1]
    • ['foo', 'bar'][0, 1][0, 1]
    • ['a' => 2, 99 => 'bar']['a', 99][0, 1]

See that only example 2 (... => [0, 1] => [0, 1]) had the same results, this makes consider as "sequence".

However, if you want the array to have only a few indices, for example: [ 0 => 'foo', 9 => 'bar' ] then you can use array_filter with is_string, this worked when used $arr = array(1 => 'bigown', 3 => 'bacco', 2 => 'gabe');, example maintained by testing issues:

//Checa se é sequencial
function is_seq($array) {
    $result = array_filter(array_keys($array), function ($value) {
        return is_int($value) === false;
    });

    return empty($result);
}

//Checa se é associativa
function is_assoc($array) {
    return !is_seq($array);
}

Or you can do it like this:

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

Example of use:

$assoc = array('a' => 1, 2, 3, 4);
$list = array(1, 2, 3, 4);
$arr = array(1 => 'bigown', 3 => 'bacco', 2 => 'gabe');

var_dump('$assoc is_assoc: ', is_assoc($assoc));
var_dump('$assoc is_seq: ', is_seq($assoc));

var_dump('$list is_assoc: ', is_assoc($list));
var_dump('$list is_seq: ', is_seq($list));

var_dump('$list is_assoc: ', is_assoc($arr));
var_dump('$list is_seq: ', is_seq($arr));

ideone: https://ideone.com/WSOKiB


Performance tests:

  • Wallace: 0.0075829029083252ms
  • Anderson: 0.037861824035645ms
  • Guilherme1: 0.026437997817993ms
  • Guilherme2: 0.056728839874268ms

Wallace’s example fared better and virtually every test, an example:

<?php
$array = range(1, 800);

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

function guilherme2($array) {
    $result = array_filter(array_keys($array), function ($value) {
        return is_int($value) === false;
    });

    return empty($result);
}

function wallace($array) {
     return strpos(json_encode($array), '[') === 0;
}

function anderson($array) {
    return array_filter($array, 'is_int', ARRAY_FILTER_USE_KEY) == true;
}

function test($callback, $arr) {
    $x = microtime(true);

    for ($i=0; $i < 100; $i++) {
        $callback($arr);
    }

    echo $callback, ': ', microtime(true) - $x, 'ms', PHP_EOL;
}

test('wallace', $array);
test('anderson', $array);
test('guilherme1', $array);
test('guilherme2', $array);

2

With the array_key_exists you can do this, see:

    // array associativo
$arrayAss = array('maçã', 'numero' => 1,  'cor' => 'vermelho');

// array sequencial
$arraySeq = array('maçã', 1, 'vermelho');

// conta quantidades
$quantAss = count($arrayAss);
$quantSeq = count($arraySeq);

$tipo = "sequencial";

// aqui ele vai retornar associativo, pois o for vai passar de forma sequencial e uma hora vai dar "false" no array_key_exists...

for($x = 0; $x < $quantAss; $x ++){

    if(array_key_exists($x, $arrayAss) == false){

        $tipo = "associativo";

        break;

    }

}

echo $tipo;

in the case below it will return sequentially because the value of the array_key_exists will never be false

   $tipo = "sequencial";

   for($x = 0; $x < $quantSeq; $x ++){

    if(array_key_exists($x, $arraySeq ) == false){

        $tipo = "associativo";

        break;

    }

}

echo $tipo;

I took the test here and it worked...

of course you can increase the best way for you.

Hug!

0

A very concise solution using the solutions range and array_keys:

// true caso $arrayVar seja uma lista sequencial (indexada de 0 até N, sem gaps)
array_keys($arrayVar) === range(0, count($arrayVar) - 1);

Using the examples of the question:

  • array('item 1', 'dois' => 'item 2'); generates [0, 'dois'] === [0, 1]; // false
  • array('item 1', 'item 2'); generates [0, 1] === [0, 1]; // true

Empty array would give false since range(0, -1) generates [0, -1], if this result is right or wrong I would say it is a philosophical question with no answer ;) but if you wish true just add $arrayVar && before comparison.

  • The range and array_keys of the first array_keys (https://answall.com/a/199090/3635) have the same effect, the difference is that generating the range is soon slower.

  • This form also does not work if the indexes are not sequential or one of the elements is excluded from the array: https://repl.it/HTFo/1

  • For non sequential indices I believe that the behavior is correct since the question itself speaks "with sequential numbers". To delete (unset) depending on how you want to see is right or wrong.

  • That may be, but I believe that the "with sequential numbers" he meant array of numerical indices, not necessarily sequential. Anyway, I believe even more that it is interesting a way that works for any situation.

  • 1

    Sequential is sequential. All the answers were helpful. What I meant was that I wanted a solution to know if the PHP array is a kind of Hash or a List (similar to the Javascript array)

0

Associative or numerical array

Use the function array_filter with the is_string function of callback. Then do a count.

The array_filter function will return an array only with the associative Keys, if they exist.

Example

$array = array('item 1', 'dois' => 'item 2');

$arrayAssociativo = array_filter(array_keys($array), 'is_string');

echo count($arrayAssociativo) > 0 ? 'Associativo' : 'Numérico';
  • Between Count and Empty I don’t see many differences: https://answall.com/a/199090/3635, basically it’s the same answer.

  • The test may still fail. If I determine that the array be it [1 => 1, 5 => 5, 10 => 10], your test will return true. It is different to be sequential than to be numerical.

  • @Wallacemaxters, I assumed that by "sequential" you meant non-associative. In this case the correct would be returns "numerical". Edited.

  • @Igoralbuquerque I found that sequential was easy to understand that it was an array with indices in sequence... type 0, 1, 2, 3.. equal in Javascript...

0

I have always used the function below for this verification:

function is_assoc($array)
{

    $arr_keys  = array_keys($array);
    $arr_shift = array_shift($arr_keys);
    if(is_array($array) && !is_numeric($arr_shift))
    {
        return true;
    }

    return false;
}

Explanation of the code:

array_keys - This function has been present in php since PHP 4. Returns all keys or a part of the keys of an array.

array_shift - removes the first element of the array and returns it. It also reorders the element indices, starting the zero indices, while the string keys remain changed. It was used here only to take the first element of the array returned by array_keys.

Next on if checks whether the variable passed is an array and whether the element of the array_key was not a number.

In short, if it is an array and the index is not numerical, it is an associative array.

Improved response after Guilherme Nascimento’s comment:

function is_assoc($array)
{
    if(is_array($array))
    {
        $arr_keys  = array_keys($array);
        $arr_shift = array_shift($arr_keys);

        return !is_numeric($arr_shift); 
    }

    return false;
}
  • Suggestion, reduce from this if(is_array($array) && !is_numeric($arr_shift))&#xA; {&#xA; return true;&#xA; }&#xA;&#xA; return false; for this return is_array($array) && !is_numeric($arr_shift);

  • Actually looking well the is_array check is in a very wrong order :/, you check the is_array after using the array_keys and shift array_shift.

  • True William, I’ll make it better.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.