Como verificar se o array PHP é associativo ou seqüencial?


781

O PHP trata todas as matrizes como associativas, portanto, não há funções incorporadas. Alguém pode recomendar uma maneira bastante eficiente de verificar se uma matriz contém apenas chaves numéricas?

Basicamente, eu quero ser capaz de diferenciar entre isso:

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

e isto:

$assocArray = array('fruit1' => 'apple', 
                    'fruit2' => 'orange', 
                    'veg1' => 'tomato', 
                    'veg2' => 'carrot');

382
Há um erro no seu código: tomate é uma fruta.
Olle Härstedt

9
Este método tem advertências, mas geralmente o faço if (isset($array[0])), o que é simples e rápido. Obviamente, você deve primeiro garantir que a matriz não esteja vazia e ter algum conhecimento sobre o conteúdo possível da matriz, para que o método não possa falhar (como numérico / associativo ou não sequencial).
Gras Double

@ OlleHärstedt Não de acordo com o Supremo Tribunal dos EUA. ;-)
MC Emperor

Respostas:


622

Você fez duas perguntas que não são totalmente equivalentes:

  • Primeiramente, como determinar se uma matriz possui apenas teclas numéricas
  • Em segundo lugar, como determinar se uma matriz possui chaves numéricas seqüenciais , iniciando em 0

Considere qual desses comportamentos você realmente precisa. (Pode ser que isso funcione para seus propósitos.)

A primeira pergunta (simplesmente verificando se todas as teclas são numéricas) é respondida bem pelo capitão kurO .

Para a segunda pergunta (verificando se a matriz é zero-indexada e seqüencial), você pode usar a seguinte função:

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

32
Solução muito elegante. Observe que ele retorna VERDADEIRO no caso (ambíguo) de uma matriz vazia.
Jonathan Lidbeck

30
Eu acho que é mais útil pensar em matrizes sequenciais como um caso especial de matrizes associativas. Portanto, todo array é associativo, mas apenas alguns são seqüenciais. Portanto, uma função isSequential()faria mais sentido do que isAssoc(). Nessa função, a matriz vazia deve ser vista como seqüencial. A fórmula poderia ser array() === $arr || !isAssoc($arr).
Donquixote

18
Eu acho que isso evitaria muito potencial de tempo e memória da CPU, se alguém verificar se isset ($ arr [0]) é falso antes de extrair todas as chaves, pois é claramente associativo se a matriz não estiver vazia, mas não tiver elemento 0 posição. Como "a maioria" de matrizes associativas reais possui cadeias de caracteres como chaves, isso deve ser uma ótima otimização para o caso geral dessa função.
OderWat

10
@OderWat - Sua otimização deve ser usada em array_key_existsvez de, issetporque se o elemento zero for um valor nulo, o isset retornará falso incorretamente. Um valor nulo normalmente deve ser um valor legítimo nessa matriz.
OCDev

@MAChitgarha, sua edição mudou o comportamento da função sem nenhuma explicação do porquê, e a contradisse com a descrição na prosa acima do que realmente deveria fazer. Eu reverti.
Mark Amery

431

Para apenas verificar se a matriz possui chaves não inteiras (não se a matriz é indexada seqüencialmente ou indexada a zero):

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

Se houver pelo menos uma chave de cadeia, $arrayserá considerada uma matriz associativa.


22
Este método é muito melhor do que parece. Se count (matriz_filtrada) == contagem (matriz_ original), é uma matriz associada. Se contar (matriz_filtrada) == 0, é uma matriz indexada. Se contar (matriz_filtrada) <contar (matriz_ original), a matriz terá as teclas numéricas e de seqüência de caracteres.
Jamol

5
@MikePretzlaw , é claro , itera; não há (obviamente) nenhuma maneira possível de determinar se todas as chaves da matriz são ints sem examinar todas as chaves na matriz. Suponho que as alternativas não iterativas que devemos ver abaixo sejam como $isIndexed = array_values($arr) === $arr;? Para o qual pergunto: como você acha que array_values()funciona? Como você acha que ===aplicado a matrizes funciona? A resposta é obviamente que eles também iteram sobre a matriz.
Mark Amery

4
@ARW "O PHP parece converter tudo para um int em uma definição de matriz, se puder." Sim, é exatamente o que acontece. O maior WTF é que ele faz isso até flutuar; se você tentar, var_dump([1.2 => 'foo', 1.5 => 'bar']);descobrirá que obtém a matriz [1 => 'bar']. Não há como descobrir o tipo original de uma chave. Sim, tudo isso é horrível; As matrizes do PHP são, de longe, a pior parte da linguagem, e a maior parte dos danos é irreparável e deve-se à idéia de usar uma única construção para matrizes tradicionais e hashmaps tradicionais, sendo uma terrível desde o início.
Mark Amery

30
@MarkAmery O acima, embora simples, garante uma caminhada de 100% da matriz. Seria mais eficiente, especialmente se você estiver lidando com matrizes grandes, se você estiver verificando se há string ou int e tiver iniciado a primeira vez que encontrou. Por exemplo: function isAssociative($arr) { foreach ($arr as $key => $value) { if (is_string($key)) return true; } return false; }
Pensei

1
@ Pensei que seu código funcionava muito rápido, mas não pode detectar a matriz seqüencial . O exemplo array(1 => 'a', 0 => 'b', 2 => 'c')se tornará false(matriz seqüencial) enquanto deveria ser true(matriz associativa). toolsqa.com/data-structures/array-in-programming Não sei se a chave deve estar em ordem crescente? (0, 1, ...)
vee

132

Certamente esta é uma alternativa melhor.

<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;

52
Isso duplicará os valores na matriz, o que é potencialmente muito caro. É muito melhor examinar as chaves da matriz.
meagar

8
Eu apenas usei ==; Não acho que seja necessário === aqui. Mas, para responder ao "desabilitado e ele não funciona": depois de desabilitar o primeiro elemento, ele não é mais um array com índice inteiro começando em 0. Portanto, o IMO funciona.
grantwparks

4
Concorde com @grantwparks: uma matriz esparsa não é indexada. Curiosamente, porque não há como excluir um elemento do meio de uma matriz indexada, o PHP está basicamente declarando todas as matrizes como associativas e numéricas, é apenas uma versão 'faça a chave para mim'.
RickMeasham

7
O único problema que tenho com isso é que ===perderemos tempo verificando se os valores são iguais, mesmo que apenas estejamos interessados ​​nas chaves. Por esse motivo, prefiro a $k = array_keys( $arr ); return $k === array_keys( $k );versão.
Jesse

5
Uma observação adicional: isso falha nas matrizes especificadas com chaves numéricas que estão com defeito. eg $ myArr = array (0 => 'a', 3 => 'b', 4 => 1, 2 => 2, 1 => '3'); Um em torno do trabalho potencial está sendo executado ksort ($ arr) antes de fazer o teste
Scott

77

Muitos comentadores nesta pergunta não entendem como as matrizes funcionam no PHP. Na documentação da matriz :

Uma chave pode ser um número inteiro ou uma sequência. Se uma chave é a representação padrão de um número inteiro, ela será interpretada como tal (ou seja, "8" será interpretado como 8, enquanto "08" será interpretado como "08"). As flutuações na chave são truncadas para um número inteiro. Os tipos de matriz indexada e associativa são do mesmo tipo no PHP, que podem conter índices inteiro e string.

Em outras palavras, não existe uma chave de matriz "8" porque ela sempre será (silenciosamente) convertida no número inteiro 8. Portanto, tentar diferenciar números inteiros e seqüências numéricas é desnecessário.

Se você deseja a maneira mais eficiente de verificar uma matriz em busca de chaves não inteiras sem fazer uma cópia de parte da matriz (como array_keys () faz) ou de tudo isso (como foreach faz):

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

Isso funciona porque key () retorna NULL quando a posição atual da matriz é inválida e NULL nunca pode ser uma chave válida (se você tentar usar NULL como uma chave de matriz, ela será convertida silenciosamente em "").


Isso não funciona para chaves inteiras não sequenciais. Experimente com [2 => 'a', 4 => 'b'].
precisa saber é o seguinte

2
@ DavidJ, o que você quer dizer com "não funciona"? Ele determina com êxito que todas as chaves são números inteiros. Você está afirmando que uma matriz como a que você postou não deve ser considerada uma "matriz numérica"?
Coredumperror

7
Uma matriz não associativa deve ter chaves que variam de 0a count($array)-1, nesta ordem estrita. Uma verificação preliminar com is_array()pode ajudar. Adicione uma variável crescente para verificar a sequência de teclas: for ($k = 0, reset($array) ; $k === key($array) ; next($array)) ++$k;Isso estabelece o acordo.
ofavre

2
Usar em foreachvez de iteração explícita é duas vezes mais rápido.
ofavre

1
Se você quiser transformar isso em uma função: function isAssocStr($array) { for (reset($array); is_int(key($array)); next($array)) { if (is_null(key($array))) return false; } return true; }
GreeKatrina

39

Conforme declarado pelo OP :

PHP trata todas as matrizes como associativas

não é muito sensato (IMHO) escrever uma função que verifique se uma matriz é associativa . Então, a primeira coisa: o que é uma chave em uma matriz PHP ?

A chave pode ser um número inteiro ou uma sequência de caracteres .

Isso significa que existem três casos possíveis:

  • Caso 1. todas as teclas são numéricas / inteiras .
  • Caso 2. todas as chaves são cadeias de caracteres .
  • Caso 3. algumas chaves são seqüências de caracteres , outras são numéricas / números inteiros .

Podemos verificar cada caso com as seguintes funções.

Caso 1: todas as teclas são numéricas / inteiras .

Nota : Esta função também retorna true para matrizes vazias.

//! 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 as chaves são cadeias de caracteres .

Nota : Esta função também retorna true para matrizes vazias.

//! 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. algumas chaves são seqüências de caracteres , outras são numéricas / números inteiros .

Nota : Esta função também retorna true para matrizes vazias.

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

Segue que:


Agora, para uma matriz ser uma matriz "genuína" à qual estamos acostumados, o que significa:

  • Suas teclas são todas numéricas / inteiras .
  • Suas teclas são seqüenciais (ou seja, aumentam na etapa 1).
  • Suas teclas começam do zero .

Podemos verificar com a seguinte função.

Caso 3a. As teclas são numéricas / inteiras , seqüenciais e baseadas em zero .

Nota : Esta função também retorna true para matrizes vazias.

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

Advertências / Armadilhas (ou, fatos ainda mais peculiares sobre chaves de array em PHP)

Chaves inteiras

As chaves para essas matrizes são números inteiros :

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

Teclas de cadeia

As chaves para essas matrizes são cadeias de caracteres :

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!

Chaves inteiras que se parecem com seqüências de caracteres

Se você acha que a chave array("13" => "b")é uma string , está errado . Do documento aqui :

Sequências contendo números inteiros válidos serão convertidas para o tipo inteiro. Por exemplo, a chave "8" será realmente armazenada em 8. Por outro lado, "08" não será convertido, pois não é um número inteiro decimal válido.

Por exemplo, a chave para essas matrizes são números inteiros :

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

Mas a chave para essas matrizes são as 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.

Além do mais, de acordo com o documento ,

O tamanho de um número inteiro depende da plataforma, embora um valor máximo de cerca de dois bilhões seja o valor usual (com 32 bits assinados). As plataformas de 64 bits geralmente têm um valor máximo de cerca de 9E18, exceto o Windows, que sempre é de 32 bits. O PHP não suporta números inteiros não assinados.

Portanto, a chave para esta matriz pode ou não ser um número inteiro - depende da sua plataforma.

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

Pior ainda, o PHP tende a ser buggy se o número inteiro estiver próximo do limite 2 31 = 2.147.483.648 (veja o bug 51430 , o bug 52899 ). Por exemplo, no meu ambiente local (PHP 5.3.8 no XAMPP 1.7.7 no Windows 7), var_dump(array("2147483647" => "b"))fornece

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

mas nesta demonstração ao vivo no codepad (PHP 5.2.5), a mesma expressão fornece

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

Portanto, a chave é um número inteiro em um ambiente, mas uma sequência em outro, mesmo sendo 2147483647um número inteiro de 32 bits com sinal válido .


2
Exceto, como mencionei abaixo, envolve a criação de uma matriz duplicada para a que está sendo verificada, tornando-a muito cara para matrizes grandes e uma fonte potencial de falhas de memória em hosts compartilhados.
precisa saber é

35

Velocidade:

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

Em termos de memória:

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

a seguinte matriz: matriz (02 => 11,1,2,456); é mostrado como não tendo teclas numéricas usando o algoritmo acima, mesmo que 02 === 2
Galileo_Galilei

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

2
Esta é a única resposta (no momento do meu comentário) que pode lidar com o seguinte: $ array = array (0 => 'blah', 2 => 'sim', 3 => 'wahey')
Shabbyrobe

mas array('1'=>'asdf', '2'=>'too')será considerado como matriz associativa enquanto não é verdade (as chaves são realmente string)
Capitão Kuro

1
@CaptainkurO Você quer dizer numérico. É uma matriz associativa.
devios1

1
Esta função retornará truese as teclas forem: zero, números inteiros (somente positivo), uma sequência vazia ou qualquer combinação das opções acima, como a sequência "09". Esta função não leva em consideração a ordem das chaves. Então array(0=>'blah', 2=>'yep', 3=>'wahey'), array(0=>'blah', 2=>'yep', 1=>'wahey')e array('blah', 'yep', 'wahey')são todos associativos de acordo com essa função, enquanto array('a'=>'blah', 'b'=>'yep', 'c'=>'wahey')não são.
Pang

@CaptainkurO você está incorreto. '1' e '2' serão armazenados como números inteiros. Leia a parte citada da resposta do esquilo de 11 de maio de 2011 às 19:34. O PHP não armazena chaves de string que se parecem exatamente com números inteiros. Ele converte esses números inteiros.
Buttle Butkus

20

Na verdade, a maneira mais eficiente é assim:

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

Isso funciona porque compara as chaves (que para uma matriz seqüencial são sempre 0,1,2 etc) às chaves das chaves (que sempre serão 0,1,2 etc).


1
Inteligente, mas não bom. Por que isso é "mais eficiente"? Seria muito mais legível comparar as array_keys ($ a) com o intervalo (0, count ($ a)). A solução mais inteligente raramente é a melhor na minha experiência. Especialmente quando ser inteligente não agrega literalmente nenhum valor sobre a alternativa óbvia e limpa.
Shane H

4
Esta função retorna truepara array(1=>"a")mas falsepara array("a"=>"a"). Seria mais significativo se !=for substituído por !==.
Pang

1
@ Você está correto. Eu pensei que seu comentário certamente deveria estar errado no começo, mas, para minha surpresa, [0] == ['a']em PHP (desde 0 == 'a'e, de fato 0 == 'banana'). O ==operador do PHP é insano.
Mark Amery

2
Não é eficiente na medida em que envolve chamar array_keys vs. apenas verificar até encontrar um índice inteiro não sequencial. De qualquer forma , você está fazendo isso de qualquer maneira , mas já duplicou uma grande variedade.
precisa saber é o seguinte

17

Eu usei ambos array_keys($obj) !== range(0, count($obj) - 1)e array_values($arr) !== $arr(que são duplos um do outro, embora o segundo seja mais barato que o primeiro), mas ambos falham em matrizes muito grandes.

Isto porque array_keyse array_valuessão ambos operações muito caras (uma vez que eles constroem uma nova série de tamanho mais ou menos a do original).

A função a seguir é mais robusta que os métodos fornecidos acima:

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

Observe também que, se você não deseja diferenciar matrizes esparsas de matrizes associativas, pode simplesmente retornar 'assoc'dos dois ifblocos.

Finalmente, embora isso possa parecer muito menos "elegante" do que muitas "soluções" nesta página, na prática é muito mais eficiente. Quase qualquer array associativo será detectado instantaneamente. Somente matrizes indexadas serão verificadas exaustivamente, e os métodos descritos acima não apenas verificam matrizes indexadas exaustivamente, elas as duplicam.


13

Eu acho que as duas funções a seguir são a melhor maneira de verificar 'se uma matriz é associativa ou numérica'. Como 'numérico' pode significar apenas teclas numéricas ou apenas teclas numéricas seqüenciais, duas funções são listadas abaixo que verificam uma das condições:

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

A primeira função verifica se cada chave é um valor inteiro. A segunda função verifica se cada chave é um valor inteiro e, além disso, verifica se todas as chaves são seqüenciais iniciando em $ base, cujo padrão é 0 e, portanto, pode ser omitido se você não precisar especificar outro valor base. key ($ my_array) retorna null se o ponteiro de leitura for movido além do final da matriz, que é o que termina o loop for e faz com que a instrução após o loop for retorne true se todas as chaves forem inteiras. Caso contrário, o loop será encerrado prematuramente porque uma chave é do tipo string e a instrução após o loop for retornará false. A última função, além disso, adiciona um a $ base após cada comparação, para poder verificar se a próxima chave tem o valor correto. A comparação estrita faz com que também verifique se a chave é do tipo inteiro. A parte $ base = (int) $ base na primeira seção do loop for pode ser deixada de fora quando $ base for omitida ou se você se certificar de que ela é chamada apenas usando um número inteiro. Mas como não posso ter certeza de todos, deixei-o. A declaração é executada apenas uma vez. Penso que estas são as soluções mais eficientes:

  • Memória: Não é possível copiar dados ou intervalos de teclas. Fazer um array_values ​​ou array_keys pode parecer mais curto (menos código), mas lembre-se do que acontece em segundo plano depois que você faz a chamada. Sim, existem mais declarações (visíveis) do que em algumas outras soluções, mas não é isso que conta, é?
  • Tempo: além do fato de que copiar / extrair dados e / ou chaves também leva tempo, essa solução é mais eficiente do que fazer um foreach. Novamente, um foreach pode parecer mais eficiente para alguns, porque é mais curto na notação, mas, em segundo plano, o foreach também chama reset, key e o próximo passo é repetir. Além disso, também chama válido para verificar a condição final, que é evitada aqui devido à combinação com a verificação inteira.

Lembre-se de que uma chave de matriz pode ser apenas um número inteiro ou uma sequência de caracteres, e uma sequência estritamente numérica como "1" (mas não "01") será convertida em um número inteiro. É isso que torna a verificação de uma chave inteira a única operação necessária, além de contar se você deseja que o array seja seqüencial. Naturalmente, se is_indexed_array retornar false, o array poderá ser visto como associativo. Eu digo 'visto', porque na verdade todos eles são.


1
Esta é a melhor resposta. A definição de matriz "associativa" ou "numérica" ​​depende da situação específica.
Pato

Se foreach for menos eficiente que o método usado aqui, além do inconveniente de usar duas funções diferentes, o desempenho desta solução é melhor que o meu (o anterior). Suspeito que não, pois o foreach é recomendado como o caminho mais rápido para passar por uma matriz.
podperson 13/10/19

7

Esta função pode lidar com:

  • matriz com orifícios no índice (por exemplo, 1,2,4,5,8,10)
  • matriz com teclas "0x": por exemplo, a tecla '08' é associativa, enquanto a tecla '8' é seqüencial.

a idéia é simples: se uma das chaves NÃO é um número inteiro, é um array associativo, caso contrário, é seqüencial.

function is_asso($a){
    foreach(array_keys($a) as $key) {if (!is_int($key)) return TRUE;}
    return FALSE;
}

1
"se uma das chaves NÃO é um número inteiro, é uma matriz associativa, caso contrário, é seqüencial" - hein? Não, isso é simplesmente errado. Há espaço para discussão sobre o que constitui uma matriz "associativa", mas o significado de "sequencial" é bastante inequívoco e não é o mesmo que todas as chaves que são números.
Mark Amery

Se uma das chaves NÃO é um número inteiro, é associativa por natureza; no entanto, é seqüencial apenas se as chaves passarem de 0 - comprimento (matriz) - 1. É NUMÉRICO, no entanto, se todas as chaves forem apenas numeradas, mas podem ou pode não funcionar com muitas funções de matriz que requerem uma matriz seqüencial. Se você converter a matriz numérica com furos em sequencial executando array_values ​​(array) nela, ela será convertida em sequencial.
geilt

7

Notei duas abordagens populares para essa pergunta: uma usando array_values()e outra usando key(). Para descobrir o que é mais rápido, escrevi um pequeno 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";

A saída para o programa no PHP 5.2 no CentOS é a seguinte:

Tempo gasto com o método # 1 = 10.745ms
Tempo gasto com o método # 2 = 18.239ms

A saída no PHP 5.3 produziu resultados semelhantes. Obviamente, o uso array_values()é muito mais rápido.


referência ruim. Você não testou grandes matrizes. No meu computador, a partir de 10 mil + elementos, o método 2 é mais rápido. Tente com$arrays = Array( 'Array #1' => range(0, 50000), );
nonsensei

7

Uma maneira de abordar isso é pegar carona json_encode, que já possui seu próprio método interno de diferenciação entre uma matriz associativa e uma matriz indexada para gerar o JSON correto.

Você pode fazer isso verificando se o primeiro caractere retornado após a codificação é um {(array associativo) ou um [(array indexado).

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

O ksort () não é necessário na minha opinião. Esta solução está funcionando, mas precisa testar se $ arr é nulo e se json_encode falhar, portanto, tente / apareça. + não é realmente ótimo se $ arr for grande.
Lucbonnin 12/10/19

7

Já existem muitas respostas, mas aqui está o método em que o Laravel conta com sua classe 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;
}

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


1
O @Casey array_keys($keys)retornará uma matriz seqüencial de números (0 ... X) que possui o mesmo comprimento da matriz original. Por exemplo array_keys(["a", "b", "c"]) = [0, 1, 2]; array_keys([0, 1, 2]) = [0, 1, 2](é uma matriz seqüencial porque [0, 1, 2] !== [0, 1, 2]). Outro exemplo: array_keys(["a" => 5, "b" => 7, "c" => 10]) = ["a", "b", "c"]; array_keys(["a", "b", "c"]) = [0, 1, 2](é uma matriz associativa porque ["a", "b", "c"] !== [0, 1, 2]). Espero que seja claro (difícil de explicar extensivamente em um comentário, pelo menos para mim)
valepu

Esse algoritmo é louco, fácil e compreensível.
Benyi 10/10

Isso não funcionará se você tiver uma matriz seqüencial de linhas associativas.
18718 lucburynin #

5
function array_is_assoc(array $a) {
    $i = 0;
    foreach ($a as $k => $v) {
        if ($k !== $i++) {
            return true;
        }
    }
    return false;
}

Rápido, conciso e com eficiência de memória. Sem comparações caras, chamadas de função ou cópia de matriz.


4

Usando a extensão PHP xarray

Você pode fazer isso muito rápido (cerca de 30 vezes mais rápido no PHP 5.6):

if (array_is_indexed($array)) {  }

Ou:

if (array_is_assoc($array)) {  }

3

Eu sei que é um pouco inútil adicionar uma resposta a essa fila enorme, mas aqui está uma solução O (n) legível que não requer duplicação de nenhum valor:

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

Em vez de verificar as teclas para ver se elas são todas numéricas, você itera sobre as chaves que estariam lá para uma matriz numérica e verifique se elas existem.


mais um ponto. matriz no formulário [1,2,null,4]falhará, mas é matriz correta. então eu adicionei alguns aprimoramentos em stackoverflow.com/a/25206156/501831 com array_key_existsverificação de adição )
lazycommit

-1; isset()é a ferramenta errada aqui, porque retornará false se o valor estiver definido, mas é null, como apontado por @lazycommit.
Mark Amery

3

Minha solução:

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

array_mergeem uma única matriz reindexará todas as integerchaves, mas não outras. Por exemplo:

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

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

Portanto, se uma lista (uma matriz não associativa) for criada ['a', 'b', 'c'], um valor será removido e, em unset($a[1])seguida array_merge, chamado, a lista será reindexada a partir de 0.


-1; isso está O(n)na memória adicional usada (desde que ele criou várias novas matrizes com tantos elementos quanto $array), a resposta não aborda a ambiguidade da pergunta que foi feita nem explica exatamente como está definindo uma lista / matriz não associativa e até se nenhum desses pontos for verdadeiro, não está claro que isso agregue algum valor em comparação com outras respostas já postadas.
Mark Amery

3

Após alguns testes locais de benchmarking, depuração, análise do compilador, criação de perfil e abuso do 3v4l.org para comparar outras versões (sim, recebi um aviso para parar) e comparar todas as variações que pude encontrar ...

Ofereço a você uma função de teste associativo de cenário da melhor média para o pior caso, derivada organicamente, que é, na pior das hipóteses, tão boa quanto ou melhor que todos os outros cenários de média.

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

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

2

Aqui está o método que eu 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')) );

Observe que isso não é responsável por casos especiais como:

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

unset($a[1]);

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

Desculpe, não posso ajudá-lo com isso. Também é um pouco de desempenho para matrizes de tamanho decente, pois não faz cópias desnecessárias. São essas pequenas coisas que tornam o Python e o Ruby muito mais agradáveis ​​de se escrever ...: P


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 os exemplos, que obtiveram mais pontos, não funcionam corretamente com matrizes como $array = array('foo' => 'bar', 1)


+1 Sua is_list () é a melhor resposta da IMO. Algumas pessoas não têm a menor idéia sobre o tempo e complexidade de espaço, e nativa vs PHP script função ...
e2-e4

2

Isso também funcionaria ( demo ):

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

Observe que o ponto principal desta resposta é informá-lo sobre a existência SplFixedArraye não incentivá-lo a usar exceções para esses tipos de testes.


2

Eu acho que a definição de uma matriz escalar irá variar de acordo com a aplicação. Ou seja, alguns aplicativos exigirão um senso mais estrito do que se qualifica como uma matriz escalar, e alguns aplicativos exigirão um senso mais flexível.

A seguir, apresento 3 métodos de rigidez variável.

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

2

Um mais rápido da fonte . Ajuste a codificação de json_encode(e bson_encode). O mesmo vale para javascript Array.

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

1
Por que issete array_key_exists? o último não seria suficiente?
Mcfedr

@mcfedr sim, seria - a isset()verificação aqui é completamente redundante.
Mark Amery

@mcfedr, @ mark-amery por motivos de desempenho. isset()é mais rápido que array_key_exists(). veja ilia.ws/archives/…
lazycommit

@lazycommit Vai depender da sua matriz e se é melhor com ou sem, não que seja provável que tenha uma matriz com muitos nulls, mas também não é provável que você tenha uma matriz grande o suficiente para que haja uma diferença de desempenho perceptível usando as duas verificações
mcfedr

2
se você precisa verificar se ele iria se encaixar json_encode, você poderia simplesmente verificar o primeiro símbolo da cadeia, retornado por json_encode($your_arr)- se é [ou {;-)
pilat

2

Essa poderia ser a solução?

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

A ressalva é obviamente que o cursor da matriz é redefinido, mas eu diria que provavelmente a função é usada antes que a matriz seja atravessada ou usada.


Esta função retorna false para ambos array("a", "b")e array("a", "b" => "B")como apenas verifica a primeira tecla. BTW, is_longé apenas um apelido deis_int .
Pang

1
francamente, acho que isso seria muito eficaz na grande maioria dos casos e é muito mais eficiente do que as alternativas. Se você entender as consequências desse método e perceber que funcionará para você, provavelmente é a melhor escolha.
Gershom

Isto é simplesmente errado; apenas olha para a primeira chave.
Mark Amery

@MarkAmery, a pergunta perguntou como diferenciar matrizes puramente sequenciais de matrizes puramente associativas. Essa resposta faz exatamente isso e é a mais eficiente de todas. Ter um comportamento indefinido para matrizes mistas é perfeitamente adequado no contexto da questão. +1
Tobia 20/07

@Tobia Eu não acho que a maioria das pessoas concordaria que você classificasse, digamos, [7 => 'foo', 2 => 'bar']como uma matriz "mista" que é parcialmente, mas não "puramente" seqüencial. Parece-me um uso claramente incorreto de palavras para mim.
Mark Amery

2

Muitas soluções aqui são elegantes e bonitas, mas não se adaptam bem e consomem muita memória ou CPU. A maioria está criando 2 novos pontos de dados na memória com esta solução dos dois lados da comparação. Quanto maior a matriz, mais difícil e mais demorado o processo e a memória utilizados, e você perde o benefício da avaliação de curto-circuito. Eu fiz alguns testes com algumas idéias diferentes. Tentando evitar array_key_exists, pois é caro, e também evitando a criação de novos conjuntos de dados grandes para comparar. Eu sinto que esta é uma maneira simples de saber se uma matriz é seqüencial.

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

Você executa uma única contagem na matriz principal e armazena um único número inteiro. Em seguida, você percorre a matriz e verifica a correspondência exata enquanto itera o contador. Você deve ter de 1 a contar. Se falhar, haverá um curto-circuito, o que aumenta a performance quando é falso.

Originalmente, eu fiz isso com um loop for e verificando o isset ($ arr [$ i]), mas isso não detectará chaves nulas, o que requer array_key_exists, e como sabemos, essa é a pior função a ser usada para velocidade.

Atualizando constantemente as variáveis ​​via foreach para verificar junto com o iterador que nunca cresce além do tamanho inteiro, vamos usar o PHP como otimização de memória, armazenamento em cache e coleta de lixo para manter o uso de recursos muito baixo.

Além disso, argumentarei que o uso de array_keys em um foreach é tolo quando você pode simplesmente executar $ key => $ value e verificar a chave. Por que criar o novo ponto de dados? Depois de abstrair as chaves da matriz, você consome mais memória imediatamente.


1

as respostas já são dadas, mas há muita desinformação sobre o desempenho. Eu escrevi esse pequeno script de benchmark que mostra que o método foreach é o mais rápido.

Isenção de responsabilidade: os métodos a seguir foram copiados e colados das outras respostas

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

1

Ou você pode simplesmente usar isso:

Arr::isAssoc($array)

que verificará se a matriz contém alguma chave não numérica ou:

Arr:isAssoc($array, true)

para verificar se a matriz é estritamente sequencial (contém chaves int geradas automaticamente de 0 a n-1 )

usando esta biblioteca.


0

A menos que o PHP tenha um builtin para isso, você não poderá fazer isso em menos de O (n) - enumerando todas as chaves e verificando o tipo inteiro. Na verdade, você também deseja garantir que não haja buracos; portanto, seu algoritmo pode se parecer com:

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

Mas por que se preocupar? Suponha que a matriz seja do tipo que você espera. Caso contrário, ele simplesmente explodirá na sua cara - é uma programação dinâmica para você! Teste seu código e tudo ficará bem ...


1
Normalmente, apenas assumindo que a matriz é o tipo desejado seria o caminho a percorrer. Mas, no meu caso, estou percorrendo uma matriz multidimensional e formatando a saída, dependendo do tipo de matriz que um determinado nó é.
Wilco

0

Comparo a diferença entre as chaves da matriz e as chaves do resultado de array_values ​​() da matriz, que sempre será uma matriz com índices inteiros. Se as chaves são as mesmas, não é uma matriz associativa.

function isHash($array) {
    if (!is_array($array)) return false;
    $diff = array_diff_assoc($array, array_values($array));
    return (empty($diff)) ? false : true;
}

-1; isso usa O(n)memória adicional quando $arraypossui nitens, e escrever em (someboolean) ? false : truevez de !somebooleané horrível e gratuito.
Mark Amery
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.