¿Cómo verificar si la matriz PHP es asociativa o secuencial?

781

PHP trata todas las matrices como asociativas, por lo que no hay funciones integradas. ¿Alguien puede recomendar una forma bastante eficiente de verificar si una matriz contiene solo teclas numéricas?

Básicamente, quiero poder diferenciar entre esto:

$sequentialArray = array('apple', 'orange', 'tomato', 'carrot');

y esto:

$assocArray = array('fruit1' => 'apple', 
                    'fruit2' => 'orange', 
                    'veg1' => 'tomato', 
                    'veg2' => 'carrot');
Wilco
fuente
382
Hay un error en su código: el tomate es una fruta.
Olle Härstedt
99
Este método tiene advertencias, pero a menudo lo hago if (isset($array[0])), lo cual es simple y rápido. Por supuesto, primero debe asegurarse de que la matriz no esté vacía, y debe tener algún conocimiento sobre los posibles contenidos de la matriz para que el método no pueda fallar (como numérico mixto / asociativo o no secuencial).
Gras Double
@ OlleHärstedt No según el Tribunal Superior de los Estados Unidos. ;-)
MC Emperor

Respuestas:

622

Has hecho dos preguntas que no son del todo equivalentes:

  • En primer lugar, cómo determinar si una matriz solo tiene teclas numéricas
  • En segundo lugar, cómo determinar si una matriz tiene claves numéricas secuenciales , comenzando desde 0

Considere cuál de estos comportamientos realmente necesita. (Puede ser que lo haga para sus propósitos).

La primera pregunta (simplemente comprobando que todas las teclas son numéricas) es respondida bien por el Capitán KurO .

Para la segunda pregunta (verificar si la matriz está indexada a cero y secuencial), puede usar la siguiente función:

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
Mark Amery
fuente
32
Solución muy elegante. Tenga en cuenta que devuelve VERDADERO en el caso (ambiguo) de una matriz vacía.
Jonathan Lidbeck
30
Creo que es más útil pensar en las matrices secuenciales como un caso especial de matrices asociativas. Por lo tanto, cada matriz es asociativa, pero solo algunas son secuenciales. Por lo tanto, una función isSequential()tendría más sentido que isAssoc(). En tal función, la matriz vacía debe verse como secuencial. La fórmula podría ser array() === $arr || !isAssoc($arr).
donquixote
18
Creo que esto evitaría mucho tiempo potencial de CPU y memoria si uno verificara si isset ($ arr [0]) es falso antes de extraer todas las claves, ya que es claramente asociativo si la matriz no está vacía pero no tiene ningún elemento en 0 posición. Como "la mayoría" de las matrices asociativas reales tienen cadenas como teclas, esta debería ser una buena optimización para el caso general de dicha función.
OderWat
10
@OderWat: su optimización debería usarse en array_key_existslugar de issetporque si el elemento cero es un valor nulo, el valor isset devolverá falso incorrectamente. Un valor nulo normalmente debería ser un valor legítimo en dicha matriz.
OCDev
@MAChitgarha, su edición cambió el comportamiento de la función sin ninguna explicación de por qué, y contradice la descripción en la prosa anterior de lo que se supone que debe hacer. Lo he revertido.
Mark Amery
431

Para verificar simplemente si la matriz tiene claves no enteras (no si la matriz está indexada secuencialmente o indexada a cero):

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

Si hay al menos una clave de cadena, $arrayse considerará como una matriz asociativa.

Capitán kurO
fuente
22
Este método es mucho mejor de lo que parece. Si count (filter_array) == count (original_array), entonces es una matriz assoc. Si count (filter_array) == 0, entonces es una matriz indexada. Si cuenta (matriz_filtrada) <cuenta (matriz_ original), entonces la matriz tiene teclas numéricas y de cadena.
Jamol
55
@MikePretzlaw, por supuesto , itera; (obviamente) no hay forma posible de determinar si todas las claves de la matriz son ints sin mirar todas las claves de la matriz. Supongo que las alternativas no iterativas que se supone que veremos a continuación son similares $isIndexed = array_values($arr) === $arr;. A lo que pregunto: ¿cómo crees que array_values()funciona? ¿Cómo crees que funciona ===aplicado a las matrices? La respuesta es, por supuesto, que también iteran sobre la matriz.
Mark Amery
44
@ARW "PHP parece transmitir todo a un int en una definición de matriz si puede". - Sí, eso es exactamente lo que pasa. El mayor WTF es que incluso hace esto a los flotadores; si lo intentas var_dump([1.2 => 'foo', 1.5 => 'bar']);, descubrirás que obtienes la matriz [1 => 'bar']. No hay forma de averiguar el tipo original de una clave. Sí, todo esto es horrible; Las matrices de PHP son, con mucho, la peor parte del lenguaje, y la mayor parte del daño es irreparable y se debe a la idea de usar una sola construcción para las matrices tradicionales y los hashmaps tradicionales desde el principio.
Mark Amery
30
@MarkAmery Lo anterior, aunque simple, garantiza una caminata del 100% de la matriz. Sería más eficiente, especialmente si se trata de matrices grandes, si estaba buscando cadenas o int y estalló en el primero que encontró. Por ejemplo: function isAssociative($arr) { foreach ($arr as $key => $value) { if (is_string($key)) return true; } return false; }
Pensé
1
@ Pensó Su código funciona muy rápido pero no puede detectar una matriz secuencial . El ejemplo array(1 => 'a', 0 => 'b', 2 => 'c')se convertirá en false(matriz secuencial) mientras que debería ser true(matriz asociativa). toolsqa.com/data-structures/array-in-programming No estoy seguro de si la clave debe estar en orden ascendente. (0, 1, ...)
vee
132

Seguramente esta es una mejor alternativa.

<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;
Dave Marshall
fuente
52
Esto duplicará los valores en la matriz, lo que es potencialmente muy costoso. Es mucho mejor examinar las teclas de matriz.
Meagar
8
Acabo de usar ==; No creo que sea necesario === aquí. Pero para responder el "desarmado y no funciona": una vez que desarma el primer elemento, ya no se trata de una matriz indexada con enteros que comienza en 0. Así que IMO funciona.
grantwparks
44
De acuerdo con @grantwparks: una matriz dispersa no está indexada. Curiosamente, porque no hay forma de eliminar realmente un elemento del medio de una matriz indexada PHP básicamente declara todas las matrices como asociativas y numéricas es solo una versión de 'inventar la clave para mí'.
RickMeasham
77
El único problema que tengo con esto es que ===perderá tiempo comprobando si los valores son iguales, aunque solo nos interesen las claves. Por esta razón, prefiero la $k = array_keys( $arr ); return $k === array_keys( $k );versión.
Jesse
55
Una nota adicional, esto falla en las matrices especificadas con teclas numéricas que están fuera de servicio. por ejemplo, $ myArr = array (0 => 'a', 3 => 'b', 4 => 1, 2 => 2, 1 => '3'); Una posible solución es ejecutar ksort ($ arr) antes de hacer la prueba
Scott,
77

Muchos comentaristas en esta pregunta no entienden cómo funcionan las matrices en PHP. De la documentación de la matriz :

Una clave puede ser un número entero o una cadena. Si una clave es la representación estándar de un número entero, se interpretará como tal (es decir, "8" se interpretará como 8, mientras que "08" se interpretará como "08"). Los flotantes en clave se truncan a entero. Los tipos de matriz indexada y asociativa son del mismo tipo en PHP, que pueden contener índices enteros y de cadena.

En otras palabras, no existe una clave de matriz de "8" porque siempre se convertirá (silenciosamente) al entero 8. Por lo tanto, no es necesario diferenciar entre enteros y cadenas numéricas.

Si desea la forma más eficiente de verificar una matriz para claves no enteras sin hacer una copia de parte de la matriz (como lo hace array_keys ()) o todo (como lo hace foreach):

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

Esto funciona porque la tecla () devuelve NULL cuando la posición actual de la matriz no es válida y NULL nunca puede ser una clave válida (si intenta usar NULL como una clave de matriz, se convierte silenciosamente a "").

ardilla
fuente
Esto no funciona para claves enteras no secuenciales. Pruébelo con [2 => 'a', 4 => 'b'].
DavidJ
2
@DavidJ, ¿qué quieres decir con "no funciona"? Determina con éxito que todas las claves son enteros. ¿Está afirmando que una matriz como la que publicó no debería considerarse una "matriz numérica"?
coredumperror
77
Una matriz no asociativa debe tener claves que van desde 0hasta count($array)-1, en este estricto orden. Una verificación preliminar con is_array()puede ayudar. Agregue una variable creciente para verificar la secuencia de teclas: for ($k = 0, reset($array) ; $k === key($array) ; next($array)) ++$k;eso resuelve el trato.
ofavre
2
El uso en foreachlugar de la iteración explícita es aproximadamente dos veces más rápido.
ofavre
1
Si quieres convertir esto en una función: function isAssocStr($array) { for (reset($array); is_int(key($array)); next($array)) { if (is_null(key($array))) return false; } return true; }
GreeKatrina
39

Como lo indica el OP :

PHP trata todas las matrices como asociativas

no es del todo razonable (en mi humilde opinión) escribir una función que verifique si una matriz es asociativa . Entonces, primero lo primero: ¿qué es una clave en una matriz PHP ?

La clave puede ser un número entero o una cadena .

Eso significa que hay 3 casos posibles:

  • Caso 1. todas las claves son numéricas / enteras .
  • Caso 2. todas las claves son cadenas .
  • Caso 3. algunas teclas son cadenas , algunas teclas son numéricas / enteras .

Podemos verificar cada caso con las siguientes funciones.

Caso 1: todas las claves son numéricas / enteras .

Nota : Esta función también devuelve verdadero para matrices vacías.

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

Caso 2: todas las teclas son cadenas .

Nota : Esta función también devuelve verdadero para matrices vacías.

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

Caso 3. algunas teclas son cadenas , algunas teclas son numéricas / enteras .

Nota : Esta función también devuelve verdadero para matrices vacías.

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

Resulta que:


Ahora, para que una matriz sea una matriz "genuina" a la que todos estamos acostumbrados, lo que significa:

  • Sus claves son todas numéricas / enteras .
  • Sus claves son secuenciales (es decir, aumentan en el paso 1).
  • Sus claves comienzan desde cero .

Podemos verificar con la siguiente función.

Caso 3a. Las teclas son numéricas / enteras , secuenciales y de base cero .

Nota : Esta función también devuelve verdadero para matrices vacías.

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

Advertencias / trampas (o, hechos aún más peculiares sobre las claves de matriz en PHP)

Teclas enteras

Las claves para estas matrices son enteros :

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

Teclas de cadena

Las claves para estas matrices son cadenas :

array("fish and chips" => "b");
array("" => "b");                                   // An empty string is also a string.
array("[email protected]" => "b");    // Strings may contain non-alphanumeric characters.
array("stack\t\"over\"\r\nflow's cool" => "b");     // Strings may contain special characters.
array('$tα€k↔øv∈rflöw⛄' => "b");                    // Strings may contain all kinds of symbols.
array("functіon" => "b");                           // You think this looks fine? Think again! (see https://stackoverflow.com/q/9246051/1402846)
array("ま말轉转ДŁ" => "b");                         // How about Japanese/Korean/Chinese/Russian/Polish?
array("fi\x0sh" => "b");                            // Strings may contain null characters.
array(file_get_contents("https://www.google.com/images/nav_logo114.png") => "b");   // Strings may even be binary!

Teclas enteras que parecen cuerdas

Si cree que la clave array("13" => "b")es una cadena , está equivocado . Del documento aquí :

Las cadenas que contienen enteros válidos se convertirán al tipo entero. Por ejemplo, la clave "8" se almacenará realmente en 8. Por otro lado, "08" no se lanzará, ya que no es un entero decimal válido.

Por ejemplo, la clave para estas matrices son enteros :

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

Pero la clave para estas matrices son las cadenas :

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.

Además, según el documento ,

El tamaño de un número entero depende de la plataforma, aunque un valor máximo de aproximadamente dos mil millones es el valor habitual (es decir, 32 bits con signo). Las plataformas de 64 bits generalmente tienen un valor máximo de aproximadamente 9E18, excepto Windows, que siempre es de 32 bits. PHP no admite enteros sin signo.

Por lo tanto, la clave para esta matriz puede o no ser un número entero ; depende de su plataforma.

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

Peor aún, PHP tiende a ser errores si el número entero está cerca del límite 2 31 = 2,147,483,648 (ver error 51430 , error 52899 ). Por ejemplo, en mi entorno local (PHP 5.3.8 en XAMPP 1.7.7 en Windows 7), var_dump(array("2147483647" => "b"))da

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

pero en esta demostración en vivo en el teclado (PHP 5.2.5), la misma expresión da

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

Entonces la clave es un número entero en un entorno pero una cadena en otro, aunque 2147483647es un número entero de 32 bits con signo válido .

Pang
fuente
2
Excepto, como menciono a continuación, implica crear una matriz duplicada para la que se está verificando, lo que lo hace muy costoso para matrices grandes y una fuente potencial de fallas de memoria en hosts compartidos.
podperson
35

En cuanto a la velocidad:

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

En cuanto a la memoria:

function isAssoc($array)
{
    $array = array_keys($array); return ($array !== array_keys($array));
}
Alix Axel
fuente
la siguiente matriz: matriz (02 => 11,1,2,456); se muestra como que no tiene teclas numéricas usando el algoritmo anterior, incluso si 02 === 2
Galileo_Galilei
20
function checkAssoc($array){
    return  ctype_digit( implode('', array_keys($array) ) );
}
revs dsims
fuente
2
Esta es la única respuesta (en el momento de mi comentario) que puede tratar con lo siguiente: $ array = array (0 => 'blah', 2 => 'yep', 3 => 'wahey')
Shabbyrobe
pero array('1'=>'asdf', '2'=>'too')será considerado como matriz asociativa, mientras que en realidad no es (son en realidad las teclas de cuerdas)
Capitán Kuro
1
@CaptainkurO Te refieres a numérico. Es una matriz asociativa.
devios1
1
Esta función se devuelve truesi las claves son: cero, enteros (solo positivos), una cadena vacía o cualquier combinación de las anteriores, como la cadena "09". Esta función no tiene en cuenta el orden de las teclas. Entonces array(0=>'blah', 2=>'yep', 3=>'wahey'), array(0=>'blah', 2=>'yep', 1=>'wahey')y array('blah', 'yep', 'wahey')todos son asociativos de acuerdo con esta función, mientras array('a'=>'blah', 'b'=>'yep', 'c'=>'wahey')que no lo es.
Pang
@CaptainkurO eres incorrecto. '1' y '2' se almacenarán como enteros. Lea la parte citada de la respuesta de la ardilla del 11 de mayo de 2011 a las 19:34. PHP no almacena claves de cadena que se vean exactamente como enteros. Convierte esos en enteros.
Buttle Butkus
20

En realidad, la forma más eficiente es así:

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

Esto funciona porque compara las claves (que para una matriz secuencial son siempre 0,1,2, etc.) con las claves de las claves (que siempre serán 0,1,2, etc.).

anon
fuente
1
Inteligente, pero no bueno. ¿Por qué es esto "más eficiente"? Sería mucho más legible comparar simplemente array_keys ($ a) con range (0, count ($ a)). La solución más inteligente rara vez es la mejor en mi experiencia. Especialmente cuando ser inteligente no agrega literalmente ningún valor sobre la alternativa obvia y limpia.
Shane H
44
Esta función devuelve truepara array(1=>"a")pero falsepara array("a"=>"a"). Sería más significativo si !=se reemplaza por !==.
Pang
1
@Pang tienes razón. Pensé que tu comentario seguramente debe estar equivocado al principio, pero, para mi sorpresa, [0] == ['a']en PHP (desde entonces 0 == 'a'y, de hecho 0 == 'banana'). El ==operador de PHP está loco.
Mark Amery
2
No es eficiente en la medida en que implica llamar a array_keys en lugar de simplemente verificar hasta que encuentre un índice entero no secuencial. De todos modos , lo estás haciendo de todos modos , pero ya has duplicado una gran variedad.
podperson
17

He usado ambos array_keys($obj) !== range(0, count($obj) - 1)y array_values($arr) !== $arr(que son duales entre sí, aunque el segundo es más barato que el primero) pero ambos fallan para matrices muy grandes.

Esto es porque array_keysyarray_values son ambas operaciones muy costosas (ya que construir toda una nueva gama de tamaño más o menos a la del original).

La siguiente función es más robusta que los métodos proporcionados anteriormente:

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

También tenga en cuenta que si no le importa diferenciar las matrices dispersas de las matrices asociativas, simplemente puede regresar 'assoc'de ambos ifbloques.

Finalmente, si bien esto puede parecer mucho menos "elegante" que muchas "soluciones" en esta página, en la práctica es mucho más eficiente. Casi cualquier matriz asociativa se detectará al instante. Solo las matrices indexadas se verificarán exhaustivamente, y los métodos descritos anteriormente no solo verifican las matrices indexadas exhaustivamente, sino que las duplican.

podperson
fuente
13

Creo que las siguientes dos funciones son la mejor manera de verificar "si una matriz es asociativa o numérica". Dado que 'numérico' podría significar solo teclas numéricas o solo teclas numéricas secuenciales, a continuación se enumeran dos funciones que verifican cualquiera de las condiciones:

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

La primera función verifica si cada clave es un valor entero. La segunda función verifica si cada clave es un valor entero y además verifica si todas las claves son secuenciales comenzando en $ base, que por defecto es 0 y, por lo tanto, puede omitirse si no necesita especificar otro valor base. La tecla ($ my_array) devuelve nulo si el puntero de lectura se mueve más allá del final de la matriz, que es lo que termina el ciclo for y hace que la declaración después del ciclo for sea verdadera si todas las claves son enteras. Si no, el ciclo finaliza prematuramente porque una clave es de tipo cadena, y la declaración después del ciclo for devolverá falso. La última función además agrega uno a $ base después de cada comparación, para poder verificar si la siguiente clave es del valor correcto. La comparación estricta hace que también verifique si la clave es de tipo entero. La parte $ base = (int) $ base en la primera sección del bucle for se puede omitir cuando se omite $ base o si se asegura de que solo se llame usando un número entero. Pero como no puedo estar seguro para todos, lo dejé. La declaración se ejecuta solo una vez, de todos modos. Creo que estas son las soluciones más eficientes:

  • En cuanto a la memoria: no se copian datos ni rangos de teclas. Hacer un array_values ​​o array_keys puede parecer más corto (menos código) pero tenga en cuenta lo que sucede en segundo plano una vez que realiza esa llamada. Sí, hay más declaraciones (visibles) que en algunas otras soluciones, pero eso no es lo que cuenta, ¿verdad?
  • En cuanto al tiempo: además del hecho de que copiar / extraer datos y / o claves también lleva tiempo, esta solución es más eficiente que hacer un foreach. Una vez más, un foreach puede parecer más eficiente para algunos porque es más corto en notación, pero en segundo plano foreach también llama a reset, key y luego para hacer un bucle. Pero además, también es válido para verificar la condición final, lo que se evita aquí debido a la combinación con la verificación de enteros.

Recuerde que una clave de matriz solo puede ser un entero o una cadena, y una cadena estrictamente numérica como "1" (pero no "01") se traducirá en un entero. Lo que hace que la comprobación de una clave entera sea la única operación necesaria además de contar si desea que la matriz sea secuencial. Naturalmente, si is_indexed_array devuelve falso, la matriz puede verse como asociativa. Digo "visto", porque de hecho todos lo son.

Niels Ockeloen
fuente
1
Esta es la mejor respuesta. La definición de matriz "asociativa" o "numérica" ​​depende de la situación específica.
Pato
Si foreach es menos eficiente que el método utilizado aquí, además de los inconvenientes de usar dos funciones diferentes, el rendimiento de esta solución es mejor que el mío (el anterior). Sospecho que no lo es, ya que foreach se recomienda como la forma más rápida de pasar por una matriz.
podperson
7

Esta función puede manejar:

  • matriz con agujeros en el índice (por ejemplo, 1,2,4,5,8,10)
  • matriz con teclas "0x": por ejemplo, la tecla '08' es asociativa mientras que la tecla '8' es secuencial.

La idea es simple: si una de las claves NO es un número entero, es una matriz asociativa, de lo contrario es secuencial.

function is_asso($a){
    foreach(array_keys($a) as $key) {if (!is_int($key)) return TRUE;}
    return FALSE;
}
revs LazNiko
fuente
1
"si una de las claves NO es un entero, es una matriz asociativa, de lo contrario es secuencial" - ¿eh? No, esto simplemente está mal. Hay espacio para discutir sobre lo que constituye una matriz "asociativa", pero el significado de "secuencial" es bastante inequívoco, y no es lo mismo que todas las claves son números.
Mark Amery el
Si una de las claves NO es un entero, ES asociativa por naturaleza, sin embargo, solo es secuencial si las claves van desde 0 - longitud (matriz) - 1. Sin embargo, es NUMÉRICO, si todas las claves solo están numeradas, pero pueden o puede no funcionar con muchas funciones de matriz que requieren una matriz secuencial. Si convierte la matriz numérica con agujeros en secuencial ejecutando array_values ​​(array) en ella, se convertiría en secuencial.
Dorado
7

Noté dos enfoques populares para esta pregunta: uno usando array_values()y otro usando key(). Para averiguar cuál es más rápido, escribí un pequeño programa:

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

La salida para el programa en PHP 5.2 en CentOS es la siguiente:

Tiempo tomado con el método # 1 = 10.745ms
Tiempo tomado con el método # 2 = 18.239ms

La salida en PHP 5.3 arrojó resultados similares. Obviamente usar array_values()es mucho más rápido.

Manu Manjunath
fuente
mal punto de referencia. No probaste matrices grandes. En mi computadora a partir de 10K + elementos, el método # 2 es más rápido. Probar con$arrays = Array( 'Array #1' => range(0, 50000), );
sin sentido
7

Una forma de abordar esto es a cuestas json_encode, que ya tiene su propio método interno de diferenciar entre una matriz asociativa y una matriz indexada para generar el JSON correcto.

Puede hacerlo comprobando si el primer carácter devuelto después de la codificación es una {(matriz asociativa) o una [(matriz indexada).

// Too short :)
function is_assoc($arr) {
    ksort($arr);
    return json_encode($arr)[0] === '{';
}
MAChitgarha
fuente
El ksort () no es necesario en mi opinión. Esta solución está funcionando pero tiene que probar si $ arr es nulo y si json_encode falla, por lo que un intento / captura. + no es realmente óptimo si $ arr es grande.
lucbonnin
7

Ya hay muchas respuestas, pero aquí está el método en el que se basa Laravel dentro de su clase Arr:

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

Fuente: https://github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Arr.php

Ben
fuente
1
@Casey array_keys($keys)devolverá una matriz secuencial de números (0 ... X) que tiene la misma longitud que la matriz original. Por ejemplo array_keys(["a", "b", "c"]) = [0, 1, 2]; array_keys([0, 1, 2]) = [0, 1, 2](es una matriz secuencial porque [0, 1, 2] !== [0, 1, 2]). Otro ejemplo: array_keys(["a" => 5, "b" => 7, "c" => 10]) = ["a", "b", "c"]; array_keys(["a", "b", "c"]) = [0, 1, 2](es una matriz asociativa porque ["a", "b", "c"] !== [0, 1, 2]). Espero que sea claro (difícil de explicar ampliamente en un comentario, al menos para mí)
valepu
Este algoritmo es loco, fácil, comprensible.
Benyi
Esto no funcionará si tiene una matriz secuencial de filas asociativas.
lucbonnin
5
function array_is_assoc(array $a) {
    $i = 0;
    foreach ($a as $k => $v) {
        if ($k !== $i++) {
            return true;
        }
    }
    return false;
}

Rápido, conciso y eficiente en memoria. Sin comparaciones costosas, llamadas a funciones o copia de matrices.

Jesse
fuente
4

Mediante el uso de la extensión PHP xarray

Puede hacer esto muy rápido (aproximadamente 30 veces más rápido en PHP 5.6):

if (array_is_indexed($array)) {  }

O:

if (array_is_assoc($array)) {  }
revs c9s
fuente
3

Sé que es un poco inútil agregar una respuesta a esta gran cola, pero aquí hay una solución legible de O (n) que no requiere duplicar ningún valor:

function isNumericArray($array) {
    $count = count($array);
    for ($i = 0; $i < $count; $i++) {
        if (!isset($array[$i])) {
            return FALSE;
        }
    }
    return TRUE;
}

En lugar de verificar las teclas para ver si son todas numéricas, itera sobre las teclas que estarían allí para una matriz numérica y asegúrese de que existan.

pie de nube
fuente
Un punto más. La matriz en forma [1,2,null,4]fallará, pero es la matriz correcta. así que he agregado algunas mejoras en stackoverflow.com/a/25206156/501831 con array_key_existsverificación de adición )
lazycommit
-1; isset()es la herramienta incorrecta aquí porque devolverá falso si se establece el valor pero es null, como lo señala @lazycommit.
Mark Amery el
3

Mi solución:

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

array_mergeen una sola matriz reindexará todas las integerclaves, pero no otras. Por ejemplo:

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

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

Entonces, si se crea una lista (una matriz no asociativa) ['a', 'b', 'c'], se elimina un valor y unset($a[1])luego array_mergese llama, la lista se reindexa a partir de 0.

ByScripts
fuente
-1; esto se O(n)usa en memoria adicional (ya que creó múltiples matrices nuevas con tantos elementos como sea posible $array), la respuesta no aborda la ambigüedad de la pregunta que se hace ni explica exactamente cómo está definiendo una lista / matriz no asociativa, e incluso Si ninguno de estos puntos fuera cierto, no está claro que esto agregue algún valor en comparación con otras respuestas ya publicadas.
Mark Amery
3

Después de algunas evaluaciones comparativas locales, depuración, pruebas de compilación, creación de perfiles y abuso de 3v4l.org para comparar en más versiones (sí, recibí una advertencia para detener) y comparar con cada variación que pude encontrar ...

Le doy una función de prueba de matriz asociativa de mejor escenario promedio peor caso derivada orgánicamente que en el peor de los casos es aproximadamente tan buena o mejor que todos los demás escenarios de caso promedio.

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

Desde 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";
}
TylerY86
fuente
2

Aquí está el método que uso:

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

Tenga en cuenta que esto no tiene en cuenta casos especiales como:

$a = array( 1, 2, 3, 4 );

unset($a[1]);

assert( true === is_associative($a) );

Lo siento, no puedo ayudarte con eso. También es algo eficaz para matrices de tamaño decente, ya que no hace copias innecesarias. Son estas pequeñas cosas las que hacen que Python y Ruby sean mucho más agradables para escribir ...: P

AL la X
fuente
2
<?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);
}

?>

Ambos ejemplos, que obtuvieron la mayor cantidad de puntos, no funcionan correctamente con matrices como $array = array('foo' => 'bar', 1)

KillEveryBody
fuente
+1 Your is_list () es IMO la mejor respuesta. Algunas personas no tienen ni idea sobre la complejidad del tiempo y el espacio, y la función de script nativo vs PHP ...
e2-e4
2

Esto también funcionaría ( demo ):

function array_has_numeric_keys_only(array $array)
{
    try {
        SplFixedArray::fromArray($array, true);
    } catch (InvalidArgumentException $e) {
        return false;
    }
    return true;
}

Tenga en cuenta que el punto principal de esta respuesta es informarle sobre la existencia SplFixedArrayy no alentarlo a usar Excepciones para este tipo de pruebas.

Gordon
fuente
2

Creo que la definición de una matriz escalar variará según la aplicación. Es decir, algunas aplicaciones requerirán un sentido más estricto de lo que califica como una matriz escalar, y algunas aplicaciones requerirán un sentido más laxo.

A continuación presento 3 métodos de rigurosidad variable.

<?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 <[email protected]>
 */

/**
 * 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;
}
David Farrell
fuente
2

Uno más rápido desde la fuente . Ajuste la codificación de json_encode(y bson_encode). También tiene conformidad con la matriz de JavaScript.

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__)
        );
    }
}
revs lazycommit
fuente
1
¿Por qué issety array_key_exists? ¿No sería esto último suficiente?
mcfedr
@mcfedr sí, lo haría: la isset()comprobación aquí es completamente redundante.
Mark Amery el
@mcfedr, @ mark-amery por razones de rendimiento. isset()es más rápido que array_key_exists(). ver ilia.ws/archives/…
lazycommit
@lazycommit Dependerá de su matriz y luego de si es mejor con o sin, no es probable que tenga una matriz con muchos nulls, pero tampoco es probable que tenga una matriz lo suficientemente grande como para que haya una notable diferencia de rendimiento mediante el uso de ambos controles
mcfedr
2
si necesita verificar si encaja json_encode, simplemente puede verificar el primer símbolo de la cadena, devuelto por json_encode($your_arr)- si es [o {;-)
pilat
2

¿Podría ser esta la solución?

  public static function isArrayAssociative(array $array) {
      reset($array);
      return !is_int(key($array));
  }

La advertencia es obviamente que el cursor de la matriz se restablece, pero yo diría que probablemente la función se usa antes de que la matriz se recorra o se use.

Kat Lim Ruiz
fuente
Esta función devuelve falso para ambos array("a", "b")y array("a", "b" => "B")ya que solo verifica la primera clave. Por cierto, is_longes solo un alias deis_int .
Pang
1
francamente, creo que esto sería muy efectivo en la gran mayoría de los casos, y es mucho más eficiente que las alternativas. Si comprende las consecuencias de este método y se da cuenta de que funcionará para usted, es probable que sea la mejor opción.
Gershom
Esto simplemente está mal; solo mira la primera clave.
Mark Amery el
@MarkAmery, la pregunta fue cómo diferenciar las matrices puramente secuenciales de las matrices puramente asociativas. Esta respuesta hace exactamente eso y es la más eficiente de todas. Tener un comportamiento indefinido para matrices mixtas está perfectamente bien en el contexto de la pregunta. +1
Tobia
@Tobia No creo que la mayoría de la gente esté de acuerdo con que usted clasifique, digamos, [7 => 'foo', 2 => 'bar']como una matriz "mixta" que es parcial pero no "puramente" secuencial. Eso me parece un uso claramente incorrecto de las palabras.
Mark Amery
2

Muchas de las soluciones aquí son elegantes y bonitas, pero no escalan bien y requieren mucha memoria o CPU. La mayoría está creando 2 nuevos puntos de datos en la memoria con esta solución desde ambos lados de la comparación. Cuanto mayor sea la matriz, más difícil y más largo será el proceso y la memoria utilizados, y perderá el beneficio de la evaluación de cortocircuito. Hice algunas pruebas con algunas ideas diferentes. Tratando de evitar array_key_exists ya que es costoso, y también evitando crear nuevos conjuntos de datos grandes para comparar. Siento que esta es una manera simple de saber si una matriz es secuencial.

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

Ejecuta un solo recuento en la matriz principal y almacena un solo entero. Luego recorre la matriz y comprueba una coincidencia exacta mientras itera el contador. Deberías tener de 1 para contar. Si falla, se producirá un cortocircuito que le dará un aumento de rendimiento cuando sea falso.

Originalmente hice esto con un bucle for y buscando isset ($ arr [$ i]), pero esto no detectará claves nulas que requieren array_key_exists, y como sabemos, esa es la peor función para usar para la velocidad.

Actualizando constantemente las variables a través de foreach para verificar junto con el iterador que nunca crece más allá de su tamaño entero, usemos PHP, está integrado en la optimización de memoria, almacenamiento en caché y recolección de basura para mantenerlo con un uso de recursos muy bajo.

Además, argumentaré que usar array_keys en un foreach es una tontería cuando simplemente puede ejecutar $ key => $ value y verificar la clave. ¿Por qué crear el nuevo punto de datos? Una vez que abstrae las claves de la matriz, ha consumido más memoria de inmediato.

geilt
fuente
1

Ya se dan respuestas, pero hay demasiada desinformación sobre el rendimiento. Escribí este pequeño script de referencia que muestra que el método foreach es el más rápido.

Descargo de responsabilidad: los siguientes métodos se copiaron de las otras respuestas

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

resultados:

==== 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
sin sentido
fuente
1

O simplemente puedes usar esto:

Arr::isAssoc($array)

que verificará si la matriz contiene alguna clave no numérica o:

Arr:isAssoc($array, true)

para verificar si la matriz es estrictamente secuencial (contiene claves int generadas automáticamente 0 a n-1 )

usando esta biblioteca

Minwork
fuente
0

A menos que PHP tenga un valor incorporado para eso, no podrá hacerlo en menos de O (n), enumerando todas las claves y verificando el tipo de entero. De hecho, también debe asegurarse de que no haya agujeros, por lo que su algoritmo podría verse así:

for i in 0 to len(your_array):
    if not defined(your-array[i]):
        # this is not an array array, it's an associative array :)

¿Pero por qué molestarse? Simplemente suponga que la matriz es del tipo que espera. Si no lo es, simplemente explotará en tu cara, ¡esa es una programación dinámica para ti! Prueba tu código y todo estará bien ...

Daren Thomas
fuente
1
Normalmente, asumir que la matriz es el tipo deseado sería el camino a seguir. Pero en mi caso, estoy recorriendo una matriz multidimensional y estoy formateando la salida dependiendo de qué tipo de matriz es un nodo dado.
Wilco
0

Comparo la diferencia entre las claves de la matriz y las claves del resultado de array_values ​​() de la matriz, que siempre será una matriz con índices enteros. Si las claves son las mismas, no es una matriz asociativa.

function isHash($array) {
    if (!is_array($array)) return false;
    $diff = array_diff_assoc($array, array_values($array));
    return (empty($diff)) ? false : true;
}
philroy
fuente
-1; esto usa O(n)memoria adicional cuando $arraytiene nelementos, y escribir en (someboolean) ? false : truelugar de !somebooleanes horrible y gratuito detallado.
Mark Amery