Come verificare se l'array PHP è associativo o sequenziale?


781

PHP tratta tutti gli array come associativi, quindi non ci sono funzioni integrate. Qualcuno può raccomandare un modo abbastanza efficiente per verificare se un array contiene solo chiavi numeriche?

Fondamentalmente, voglio essere in grado di distinguere tra questo:

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

e questo:

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

382
C'è un bug nel tuo codice: il pomodoro è un frutto.
Olle Härstedt

9
Questo metodo ha avvertimenti, ma spesso lo faccio if (isset($array[0])), che è semplice e veloce. Naturalmente, dovresti prima assicurarti che l'array non sia vuoto e dovresti avere qualche conoscenza sui possibili contenuti dell'array in modo che il metodo non possa fallire (come misto numerico / associativo o non sequenziale).
Gras Double

@ OlleHärstedt Non secondo la High Court degli Stati Uniti. ;-)
MC Emperor

Risposte:


622

Hai fatto due domande che non sono del tutto equivalenti:

  • Innanzitutto, come determinare se un array ha solo chiavi numeriche
  • In secondo luogo, come determinare se un array ha tasti numerici sequenziali , a partire da 0

Considera quali di questi comportamenti hai effettivamente bisogno. (Può darsi che entrambi lo facciano per i tuoi scopi.)

Alla prima domanda (semplicemente controllando che tutti i tasti siano numerici) viene data una buona risposta dal Capitano KurO .

Per la seconda domanda (verificare se l'array è indicizzato e sequenziale zero), è possibile utilizzare la seguente funzione:

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
Soluzione molto elegante. Si noti che restituisce VERO nel caso (ambiguo) di un array vuoto.
Jonathan Lidbeck

30
Penso che sia più utile pensare agli array sequenziali come a un caso speciale di array associativi. Quindi ogni array è associativo, ma solo alcuni sono sequenziali. Pertanto, una funzione isSequential()avrebbe più senso di isAssoc(). In tale funzione, l'array vuoto dovrebbe essere visto come sequenziale. La formula potrebbe essere array() === $arr || !isAssoc($arr).
donquixote,

18
Penso che questo eviterebbe un sacco di tempo potenziale e memoria della CPU se si controlla se isset ($ arr [0]) è falso prima di estrarre tutte le chiavi come è chiaramente associativo se l'array non è vuoto ma non ha alcun elemento in 0 posizione. Poiché "la maggior parte" delle vere matrici associative hanno stringhe come chiavi, questa dovrebbe essere una buona ottimizzazione per il caso generale di tale funzione.
OderWat,

10
@OderWat - La tua ottimizzazione dovrebbe usare array_key_existsinvece che issetse l'elemento zero è un valore nullo, il set restituirà false in modo errato. Un valore nullo dovrebbe normalmente essere un valore legittimo in tale array.
OCDev,

@MAChitgarha, la tua modifica ha cambiato il comportamento della funzione senza alcuna spiegazione del perché, e lo ha fatto contraddire la descrizione nella prosa sopra di ciò che dovrebbe effettivamente fare. L'ho ripristinato.
Mark Amery,

431

Per controllare semplicemente se l'array ha chiavi non intere (non se l'array è indicizzato in sequenza o zero):

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

Se è presente almeno una chiave di stringa, $arrayverrà considerata una matrice associativa.


22
Questo metodo è molto meglio di quanto sembri. Se count (filtered_array) == count (original_array), allora è un array assoc. Se count (filtered_array) == 0, allora è un array indicizzato. Se count (filtered_array) <count (original_array), allora l'array ha sia chiavi numeriche che stringhe.
Jamol,

5
@MikePretzlaw ovviamente itera; non c'è (ovviamente) alcun modo possibile per determinare se tutte le chiavi dell'array sono ints senza guardare tutte le chiavi dell'array. Presumo che le alternative non ripetitive che dovremmo vedere di seguito siano come $isIndexed = array_values($arr) === $arr;? A cui chiedo: come pensi che array_values()funzioni? Come pensi che funzioni ===applicato agli array? La risposta è ovviamente che anche iterano sull'array.
Mark Amery,

4
@ARW "PHP sembra trasmettere tutto a un int in una definizione di array, se possibile." - Sì, è esattamente quello che succede. Il più grande WTF è che lo fa anche per i galleggianti; se provi var_dump([1.2 => 'foo', 1.5 => 'bar']);scoprirai di ottenere l'array [1 => 'bar']. Non c'è modo di scoprire il tipo originale di una chiave. Sì, tutto ciò è orribile; Gli array di PHP sono di gran lunga la parte peggiore del linguaggio e la maggior parte del danno è irreparabile e deve l'idea di utilizzare un singolo costrutto per array tradizionali e hashap tradizionali che sono orribili sin dall'inizio.
Mark Amery,

30
@MarkAmery Quanto sopra, sebbene semplice, garantisce una camminata del 100% dell'array. Sarebbe più efficiente, specialmente se hai a che fare con array di grandi dimensioni, se stavi controllando string o int e scoppiassi il primo che hai trovato. Ad esempio: function isAssociative($arr) { foreach ($arr as $key => $value) { if (is_string($key)) return true; } return false; }
Pensato il

1
@Thought Il tuo codice funziona molto velocemente ma non è in grado di rilevare array sequenziali . L'esempio array(1 => 'a', 0 => 'b', 2 => 'c')diventerà false(array sequenziale) mentre dovrebbe essere true(array associativo). toolsqa.com/data-structures/array-in-programming Non sono sicuro che la chiave debba essere in ordine crescente? (0, 1, ...)
v.

132

Sicuramente questa è un'alternativa migliore.

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

52
Ciò duplicherà i valori nell'array, che è potenzialmente molto costoso. Stai molto meglio esaminando le chiavi dell'array.
meagar

8
Ho appena usato ==; Non penso che ci sia bisogno di === qui. Ma per rispondere a "unset e non funziona": una volta disinserito il primo elemento, non è più un array con indice intero che inizia da 0. Quindi IMO funziona.
grantwparks,

4
Concordo con @grantwparks: una matrice sparsa non è indicizzata. È interessante notare che non c'è modo di eliminare effettivamente un elemento dal mezzo di un array indicizzato PHP sta fondamentalmente dichiarando tutti gli array come associativi e numerici è solo una versione di "make up the key for me".
RickMeasham,

7
L'unico problema che ho con questo è che ===perdere tempo a controllare se i valori sono uguali, anche se siamo interessati solo alle chiavi. Per questo motivo preferisco la $k = array_keys( $arr ); return $k === array_keys( $k );versione.
Jesse,

5
Una nota aggiunta, questo non riesce su array specificati con tasti numerici non funzionanti. ad esempio $ myArr = array (0 => 'a', 3 => 'b', 4 => 1, 2 => 2, 1 => '3'); Una possibile soluzione è eseguire ksort ($ arr) prima di fare il test
Scott,

77

Molti commentatori in questa domanda non capiscono come funzionano gli array in PHP. Dalla documentazione dell'array :

Una chiave può essere un numero intero o una stringa. Se una chiave è la rappresentazione standard di un numero intero, verrà interpretata come tale (ovvero "8" verrà interpretata come 8, mentre "08" verrà interpretato come "08"). I float nella chiave vengono troncati a numeri interi. I tipi di array indicizzati e associativi sono dello stesso tipo in PHP, che possono contenere sia indici interi sia stringhe.

In altre parole, non esiste una chiave di array di "8" perché sarà sempre (silenziosamente) convertita nell'intero 8. Quindi non è necessario cercare di distinguere tra numeri interi e stringhe numeriche.

Se si desidera il modo più efficiente per controllare un array per chiavi non integer senza fare una copia di una parte dell'array (come fa array_keys ()) o tutto (come fa 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);

Ciò funziona perché key () restituisce NULL quando la posizione corrente dell'array non è valida e NULL non può mai essere una chiave valida (se si tenta di utilizzare NULL come chiave dell'array, viene automaticamente convertito in "").


Questo non funziona per le chiavi intere non sequenziali. Provalo con [2 => 'a', 4 => 'b'].
DavidJ,

2
@DavidJ, cosa intendi con "non funziona"? Determina con successo che tutte le chiavi sono numeri interi. Stai sostenendo che un array come quello che hai pubblicato non dovrebbe essere considerato un "array numerico"?
coredumperror,

7
Un array non associativo deve avere chiavi che vanno da 0a count($array)-1, in questo rigoroso ordine. Un controllo preliminare con is_array()può aiutare. Aggiungi una variabile crescente per verificare la sequenza di tasti: for ($k = 0, reset($array) ; $k === key($array) ; next($array)) ++$k;che risolve l'affare.
ofavre,

2
L'utilizzo foreachanziché l'iterazione esplicita è circa due volte più veloce.
ofavre,

1
Se vuoi trasformare questa funzione in una funzione: function isAssocStr($array) { for (reset($array); is_int(key($array)); next($array)) { if (is_null(key($array))) return false; } return true; }
GreeKatrina,

39

Come dichiarato dal PO :

PHP considera tutti gli array come associativi

non è del tutto sensato (IMHO) scrivere una funzione che controlla se un array è associativo . Quindi prima cosa: qual è la chiave in un array PHP ?:

La chiave può essere un numero intero o una stringa .

Ciò significa che ci sono 3 possibili casi:

  • Caso 1. tutti i tasti sono numerici / interi .
  • Caso 2. tutte le chiavi sono stringhe .
  • Caso 3. alcune chiavi sono stringhe , alcune chiavi sono numeriche / intere .

Possiamo verificare ogni caso con le seguenti funzioni.

Caso 1: tutti i tasti sono numerici / interi .

Nota : questa funzione restituisce true anche per gli array vuoti.

//! 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: tutte le chiavi sono stringhe .

Nota : questa funzione restituisce true anche per gli array vuoti.

//! 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. alcune chiavi sono stringhe , alcune chiavi sono numeriche / intere .

Nota : questa funzione restituisce true anche per gli array vuoti.

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

Ne consegue che:


Ora, per un array essere un array "genuino" a cui siamo tutti abituati, nel senso che:

  • Le sue chiavi sono tutte numeriche / intere .
  • I suoi tasti sono sequenziali (cioè aumentano di passaggio 1).
  • Le sue chiavi iniziano da zero .

Possiamo verificare con la seguente funzione.

Caso 3a le chiavi sono numeriche / intere , sequenziali e basate su zero .

Nota : questa funzione restituisce true anche per gli array vuoti.

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

Avvertenze / insidie ​​(o, fatti ancora più particolari sulle chiavi dell'array in PHP)

Chiavi intere

Le chiavi per questi array sono numeri interi :

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

Chiavi di stringa

Le chiavi per questi array sono stringhe :

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!

Tasti interi che sembrano stringhe

Se pensi che la chiave array("13" => "b")sia una stringa , ti sbagli . Dal documento qui :

Le stringhe che contengono numeri interi validi verranno trasmesse al tipo intero. Ad esempio, la chiave "8" verrà effettivamente archiviata sotto 8. D'altra parte "08" non verrà trasmesso, in quanto non è un numero intero decimale valido.

Ad esempio, la chiave per questi array sono numeri interi :

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

Ma la chiave per questi array sono le stringhe :

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.

Inoltre, secondo il documento ,

La dimensione di un numero intero dipende dalla piattaforma, sebbene un valore massimo di circa due miliardi sia il valore normale (ovvero 32 bit con segno). Le piattaforme a 64 bit hanno in genere un valore massimo di circa 9E18, ad eccezione di Windows, che è sempre a 32 bit. PHP non supporta numeri interi senza segno.

Quindi la chiave per questo array può essere o meno un numero intero - dipende dalla tua piattaforma.

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

Ancora peggio, PHP tende a essere difettoso se il numero intero è vicino al limite 2 31 = 2.147.483.648 (vedi bug 51430 , bug 52899 ). Ad esempio, nel mio ambiente locale (PHP 5.3.8 su XAMPP 1.7.7 su Windows 7), var_dump(array("2147483647" => "b"))

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

ma in questa demo dal vivo su codepad (PHP 5.2.5), la stessa espressione dà

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

Quindi la chiave è un numero intero in un ambiente ma una stringa in un altro, anche se 2147483647è un numero intero a 32 bit con segno valido .


2
Tranne il fatto che, come accennato di seguito, comporta la creazione di un array duplicato per quello che viene controllato, rendendolo molto costoso per array di grandi dimensioni e una potenziale fonte di crash della memoria insufficiente sugli host condivisi.
podperson,

35

Velocità-saggio:

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

Memory-saggio:

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

l'array seguente: array (02 => 11,1,2,456); viene mostrato come non avere chiavi numeriche usando l'algoritmo sopra, anche se 02 === 2
Galileo_Galilei

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

2
Questa è l' unica risposta (al momento del mio commento) che può gestire quanto segue: $ array = array (0 => 'blah', 2 => 'yep', 3 => 'wahey')
Shabbyrobe

ma array('1'=>'asdf', '2'=>'too')sarà considerato come array associativo mentre in realtà non lo è (le chiavi sono in realtà stringhe)
Captain kurO

1
@CaptainkurO Intendi numerico. È un array associativo.
devios1,

1
Questa funzione restituisce truese le chiavi sono: zero, numeri interi (solo positivi), una stringa vuota o qualsiasi combinazione di quanto sopra, come la stringa "09". Questa funzione non tiene conto dell'ordine dei tasti. Quindi array(0=>'blah', 2=>'yep', 3=>'wahey'), array(0=>'blah', 2=>'yep', 1=>'wahey')e array('blah', 'yep', 'wahey')sono tutti associativi secondo questa funzione, mentre array('a'=>'blah', 'b'=>'yep', 'c'=>'wahey')non lo è.
Pang

@CaptainkurO non sei corretto. '1' e '2' saranno memorizzati come numeri interi. Leggi la parte citata della risposta dello scoiattolo dell'11 maggio 2011 alle 19:34. PHP non memorizza le chiavi di stringa che sembrano esattamente come numeri interi. Converte quelli in numeri interi.
Buttle Butkus,

20

In realtà il modo più efficiente è quindi:

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

Questo funziona perché confronta i tasti (che per un array sequenziale sono sempre 0,1,2 ecc.) Con i tasti dei tasti (che saranno sempre 0,1,2 ecc.).


1
Intelligente, ma non buono. Perché questo "più efficiente"? Sarebbe molto più leggibile confrontare semplicemente array_keys ($ a) con range (0, count ($ a)). La soluzione più intelligente è raramente la migliore della mia esperienza. Soprattutto quando essere intelligenti non aggiunge letteralmente alcun valore rispetto all'alternativa ovvia e pulita.
Shane H,

4
Questa funzione ritorna trueper array(1=>"a")ma falseper array("a"=>"a"). Sarebbe più significativo se !=viene sostituito da !==.
Pang

1
@Pang hai ragione. Ho pensato che il tuo commento dovesse sicuramente essere sbagliato all'inizio, ma, con mia sorpresa, [0] == ['a']in PHP (poiché 0 == 'a', e, in effetti, 0 == 'banana'). L' ==operatore di PHP è pazzo.
Mark Amery,

2
Non è efficiente nella misura in cui comporta la chiamata di array_keys rispetto al solo controllo fino a trovare un indice intero non sequenziale. Sotto il cofano lo stai facendo comunque , ma hai già duplicato un grande array.
podperson,

17

Ho usato entrambi array_keys($obj) !== range(0, count($obj) - 1)e array_values($arr) !== $arr(che sono i doppi l'uno dell'altro, sebbene il secondo sia più economico del primo) ma entrambi falliscono per array molto grandi.

Questo perché array_keyse array_valuessono entrambe operazioni molto costose (dal momento che costruiscono un intero nuovo array di dimensioni all'incirca quello dell'originale).

La seguente funzione è più efficace rispetto ai metodi forniti sopra:

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

Inoltre, se non ti interessa differenziare gli array sparsi dagli array associativi, puoi semplicemente tornare 'assoc'da entrambi i ifblocchi.

Infine, sebbene ciò possa sembrare molto meno "elegante" di molte "soluzioni" in questa pagina, in pratica è molto più efficiente. Quasi qualsiasi array associativo verrà rilevato all'istante. Solo gli array indicizzati verranno controllati in modo esaustivo e i metodi descritti sopra non solo controllano in modo esaustivo gli array indicizzati, ma li duplicano.


13

Penso che le seguenti due funzioni siano il modo migliore per verificare "se un array è associativo o numerico". Poiché 'numerico' potrebbe significare solo tasti numerici o solo tasti numerici sequenziali, due funzioni sono elencate di seguito che controllano entrambe le condizioni:

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 prima funzione controlla se ogni tasto è un valore intero. La seconda funzione controlla se ogni chiave è un valore intero e inoltre controlla se tutte le chiavi sono sequenziali a partire da $ base, il cui valore predefinito è 0 e quindi può essere omesso se non è necessario specificare un altro valore base. key ($ my_array) restituisce null se il puntatore di lettura viene spostato oltre la fine dell'array, che è ciò che termina il ciclo for e rende vera l'istruzione dopo il ciclo for se tutti i tasti erano interi. In caso contrario, il ciclo termina prematuramente perché una chiave è di tipo stringa e l'istruzione dopo il ciclo for restituirà false. Quest'ultima funzione aggiunge inoltre una a $ base dopo ogni confronto, per poter verificare se la chiave successiva ha il valore corretto. Il confronto rigoroso consente inoltre di verificare se la chiave è di tipo intero. La parte $ base = (int) $ base nella prima sezione del ciclo for può essere tralasciata quando $ base viene omessa o se ci si assicura che sia chiamato solo usando un numero intero. Ma dal momento che non posso essere sicuro per tutti, l'ho lasciato dentro. L'affermazione viene eseguita solo una volta, comunque. Penso che queste siano le soluzioni più efficienti:

  • Per quanto riguarda la memoria: nessuna copia di dati o intervalli di chiavi. Fare array_values ​​o array_keys può sembrare più breve (meno codice) ma tieni presente cosa succede in background una volta effettuata la chiamata. Sì, ci sono più dichiarazioni (visibili) che in alcune altre soluzioni, ma non è questo ciò che conta, vero?
  • In termini di tempo: oltre al fatto che la copia / estrazione di dati e / o chiavi richiede anche tempo, questa soluzione è più efficiente di una ricerca. Ancora una volta un foreach può sembrare più efficiente ad alcuni perché è più breve in notazione, ma in background foreach chiama anche reset, key e next per farlo è in loop. Inoltre, chiama anche valido per verificare la condizione finale, che qui viene evitata a causa della combinazione con il controllo intero.

Ricorda che una chiave di array può essere solo un numero intero o una stringa e una stringa strettamente numerica come "1" (ma non "01") verrà tradotta in un numero intero. Questo è ciò che rende il controllo di una chiave intera l'unica operazione necessaria oltre al conteggio se si desidera che l'array sia sequenziale. Naturalmente, se is_indexed_array restituisce false, l'array può essere visto come associativo. Dico "visto", perché in realtà lo sono tutti.


1
Questa è la risposta migliore La definizione di matrice "associativa" o "numerica" ​​dipende dalla situazione specifica.
Pato

Se foreach è meno efficiente del metodo qui utilizzato, a parte l'inconveniente di utilizzare due diverse funzioni, le prestazioni di questa soluzione sono migliori delle mie (le precedenti). Ho il sospetto che non lo sia, dal momento che foreach è raccomandato come il modo più veloce per passare attraverso un array.
podperson,

7

Questa funzione può gestire:

  • matrice con fori nell'indice (ad es. 1,2,4,5,8,10)
  • array con tasti "0x": ad es. il tasto '08' è associativo mentre il tasto '8' è sequenziale.

l'idea è semplice: se una delle chiavi NON è un numero intero, è un array associativo, altrimenti è sequenziale.

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

1
"se una delle chiavi NON è un numero intero, è un array associativo, altrimenti è sequenziale" - eh? No, questo è semplicemente sbagliato. C'è spazio per litigare su ciò che costituisce un array "associativo", ma il significato di "sequenziale" è piuttosto inequivocabile, e non è lo stesso di tutti i tasti che sono numeri.
Mark Amery,

Se una delle chiavi NON è un numero intero È associativa per natura, tuttavia, è sequenziale solo se le chiavi vanno da 0 - lunghezza (array) - 1. È NUMERICO, tuttavia, se tutte le chiavi sono solo numerate, ma può o potrebbe non funzionare con molte funzioni di array che richiedono un array sequenziale. Se si converte l'array numerico con fori in sequenziale eseguendo array_values ​​(array) su di esso, verrà convertito in sequenziale.
Geilt,

7

Ho notato due approcci popolari per questa domanda: uno usando array_values()e l'altro usando key(). Per scoprire quale è più veloce, ho scritto un piccolo programma:

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

L'output per il programma su PHP 5.2 su CentOS è il seguente:

Tempo impiegato con il metodo n. 1 = 10.745 ms
Tempo impiegato con il metodo n. 2 = 18.239 ms

L'output su PHP 5.3 ha prodotto risultati simili. Ovviamente l'utilizzo array_values()è molto più veloce.


benchmark negativo. Non hai testato grandi array. Sul mio computer a partire da 10K + elementi il ​​metodo n. 2 è più veloce. Prova con$arrays = Array( 'Array #1' => range(0, 50000), );
nonsensei, il

7

Un modo per avvicinarsi a questo è di piggyback on json_encode, che ha già il suo metodo interno di differenziazione tra un array associativo e un array indicizzato al fine di produrre il JSON corretto.

Puoi farlo controllando se il primo carattere restituito dopo la codifica è un {(array associativo) o un [(array indicizzato).

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

Secondo me, ksort () non è necessario. Questa soluzione funziona ma deve verificare se $ arr è nullo e se json_encode fallisce, quindi un tentativo / catch. + non è davvero ottimale se $ arr è grande.
Lucbonnin,

7

Ci sono già molte risposte, ma ecco il metodo su cui Laravel fa affidamento nella 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
@Casey array_keys($keys)restituirà un array sequenziale di numeri (0 ... X) che ha la stessa lunghezza dell'array originale. Ad esempio array_keys(["a", "b", "c"]) = [0, 1, 2]; array_keys([0, 1, 2]) = [0, 1, 2](è un array sequenziale perché [0, 1, 2] !== [0, 1, 2]). Un altro esempio: array_keys(["a" => 5, "b" => 7, "c" => 10]) = ["a", "b", "c"]; array_keys(["a", "b", "c"]) = [0, 1, 2](è un array associativo perché ["a", "b", "c"] !== [0, 1, 2]). Spero sia chiaro (difficile da spiegare ampiamente in un commento, almeno per me)
valepu

Questo algoritmo è pazzo, facile, comprensibile.
Benyi,

Questo non funzionerà se si dispone di un array sequenziale di righe associative.
Lucbonnin,

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

Veloce, conciso ed efficiente in termini di memoria. Nessun confronto costoso, chiamate di funzione o copia di array.


4

Usando l' estensione PHP xarray

Puoi farlo molto velocemente (circa 30 volte più veloce in PHP 5.6):

if (array_is_indexed($array)) {  }

O:

if (array_is_assoc($array)) {  }

3

So che è un po 'inutile aggiungere una risposta a questa enorme coda, ma ecco una soluzione O (n) leggibile che non richiede la duplicazione di alcun valore:

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

Invece di controllare le chiavi per vedere se sono tutte numeriche, si esegue l'iterazione delle chiavi che sarebbero lì per un array numerico e si assicura che esistano.


un altro punto. l'array nella forma [1,2,null,4]fallirà, ma è l'array corretto. quindi ho aggiunto alcuni miglioramenti su stackoverflow.com/a/25206156/501831 con array_key_existscontrollo di aggiunta )
lazycommit

-1; isset()è lo strumento sbagliato qui perché restituirà false se il valore è impostato ma lo è null, come sottolineato da @lazycommit.
Mark Amery,

3

La mia soluzione:

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

array_mergesu un singolo array reindicizzerà tutte le integerchiavi, ma non altre. Per esempio:

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

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

Pertanto, se viene creato un elenco (un array non associativo) ['a', 'b', 'c'], viene rimosso un valore, unset($a[1])quindi array_mergeviene chiamato, l'elenco viene reindicizzato a partire da 0.


-1; questo viene utilizzato O(n)nella memoria aggiuntiva (poiché ha creato più nuove matrici con tanti elementi quanti $array), la risposta non affronta l'ambiguità della domanda posta né spiega esattamente come sta definendo un elenco / array non associativo, e persino se nessuno di questi punti fosse vero non è chiaro che ciò aggiunga valore rispetto ad altre risposte già pubblicate.
Mark Amery,

3

Dopo alcuni benchmark locali, debug, sondaggio del compilatore, creazione di profili e abuso di 3v4l.org per eseguire il benchmark su più versioni (sì, ho ricevuto un avvertimento per interrompere) e confrontarlo con ogni variazione che ho trovato ...

Vi offro una funzione di test di array associativo di scenario migliore nella media nel peggiore dei casi, che nel peggiore dei casi è approssimativamente buona o migliore di tutti gli altri scenari nel caso medio.

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

Da 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

Ecco il metodo che 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')) );

Nota che questo non tiene conto di casi speciali come:

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

unset($a[1]);

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

Spiacente, non posso aiutarti. È anche un po 'performante per array di dimensioni decenti, in quanto non fa copie inutili. Sono queste piccole cose che rendono Python e Ruby molto più belli da scrivere in ...: 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);
}

?>

Entrambi questi esempi, che hanno segnato il maggior numero di punti, non funzionano correttamente con array come $array = array('foo' => 'bar', 1)


+1 La tua is_list () è l'IMO la risposta migliore. Alcune persone non hanno idea della complessità del tempo e dello spazio e della funzione di script nativa vs PHP ...
e2-e4

2

Funzionerebbe anche questo ( demo ):

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

Si noti che il punto principale di questa risposta è informarvi dell'esistenza SplFixedArraye non incoraggiarvi a utilizzare le eccezioni per questo tipo di test.


2

Penso che la definizione di un array scalare varierà in base all'applicazione. Cioè, alcune applicazioni richiederanno un senso più rigoroso di ciò che si qualifica come un array scalare e alcune applicazioni richiederanno un senso più lento.

Di seguito vi presento 3 metodi di rigore variabile.

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

Un altro veloce dalla fonte . Adatta la codifica di json_encode(e bson_encode). Così ha la conformità dell'array 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__)
        );
    }
}

1
Perché issete array_key_exists? quest'ultimo non sarebbe abbastanza?
mcfedr,

@mcfedr sì, lo sarebbe - il isset()controllo qui è completamente ridondante.
Mark Amery,

@mcfedr, @ mark-amery per motivi di prestazioni. isset()è più veloce di array_key_exists(). vedi ilia.ws/archives/…
lazycommit

@lazycommit Dipenderà dal tuo array quindi dal fatto che sia migliore con o senza, non che sia probabile che abbia un array con un sacco di nulls, ma quindi non è così probabile che tu abbia un array abbastanza grande da avere una notevole differenza di prestazioni usando entrambi i controlli
mcfedr

2
se devi controllare se si adatta json_encode, potresti semplicemente controllare il primo simbolo della stringa, restituito da json_encode($your_arr)- che sia [o {;-)
pilat

2

Potrebbe essere questa la soluzione?

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

L'avvertenza è ovviamente che il cursore dell'array viene ripristinato, ma direi che probabilmente la funzione viene utilizzata prima ancora che l'array venga attraversato o utilizzato.


Questa funzione restituisce false per entrambi array("a", "b")e array("a", "b" => "B")poiché controlla solo la prima chiave. A proposito, is_longè solo un alias diis_int .
Pang

1
francamente penso che questo sarebbe molto efficace nella stragrande maggioranza dei casi ed è molto più efficiente delle alternative. Se capisci le conseguenze di questo metodo e ti rendi conto che funzionerà per te, è probabilmente la scelta migliore.
Gershom il

Questo è semplicemente sbagliato; guarda solo la prima chiave.
Mark Amery,

@MarkAmery la domanda che pone come differenziare gli array puramente sequenziali dagli array puramente associativi. Questa risposta fa esattamente questo ed è la più efficiente di tutte. Avere un comportamento indefinito per gli arrangiamenti misti va perfettamente bene nel contesto della domanda. +1
Tobia,

@Tobia Non credo che la maggior parte delle persone sarebbe d'accordo con te classificando, diciamo, [7 => 'foo', 2 => 'bar']un array "misto" che è in parte ma non "puramente" sequenziale. Mi sembra un uso chiaramente errato delle parole.
Mark Amery,

2

Molte soluzioni qui sono eleganti e carine, ma non si adattano bene e richiedono molta memoria o CPU. La maggior parte sta creando 2 nuovi punti dati in memoria con questa soluzione da entrambi i lati del confronto. Maggiore è l'array, più difficile e lungo sarà il processo e la memoria utilizzati e si perderà il vantaggio della valutazione del corto circuito. Ho fatto alcuni test con alcune idee diverse. Cercare di evitare array_key_exists in quanto è costoso e anche di evitare di creare nuovi set di dati di grandi dimensioni da confrontare. Sento che questo è un modo semplice per dire se un array è sequenziale.

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

Si esegue un singolo conteggio sull'array principale e si memorizza un singolo intero. Quindi si passa in rassegna l'array e si verifica una corrispondenza esatta durante l'iterazione del contatore. Dovresti avere da 1 a contare. Se fallisce, si verifica un cortocircuito che ti dà un aumento delle prestazioni quando è falso.

Inizialmente l'ho fatto con un ciclo for e il controllo di isset ($ arr [$ i]) ma questo non rileverà chiavi null che richiedono array_key_exists, e come sappiamo che è la funzione peggiore da usare per la velocità.

Aggiornando costantemente le variabili tramite foreach per verificare che l'iteratore non cresca mai oltre la sua dimensione intera, usiamo PHP che è integrato nell'ottimizzazione della memoria, nella memorizzazione nella cache e nella garbage collection per mantenerti a un utilizzo delle risorse molto basso.

Inoltre, sosterrò che usare array_keys in un foreach è sciocco quando puoi semplicemente eseguire $ key => $ value e controllare la chiave. Perché creare il nuovo punto dati? Dopo aver estratto le chiavi dell'array, hai immediatamente consumato più memoria.


1

le risposte sono già fornite ma c'è troppa disinformazione sulle prestazioni. Ho scritto questo piccolo script di benchmark che mostra che il metodo foreach è il più veloce.

Dichiarazione di non responsabilità: i seguenti metodi sono stati copiati e incollati dalle altre risposte

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

i risultati:

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

Oppure puoi semplicemente usare questo:

Arr::isAssoc($array)

che controllerà se l'array contiene chiavi non numeriche o:

Arr:isAssoc($array, true)

per verificare se l'array è strettamente sequenziale (contiene i tasti int generati automaticamente da 0 a n-1 )

usando questa libreria.


0

A meno che PHP non abbia un builtin per questo, non sarai in grado di farlo in meno di O (n) - enumerando tutte le chiavi e controllando il tipo intero. In effetti, vuoi anche assicurarti che non ci siano buchi, quindi il tuo algoritmo potrebbe apparire come:

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

Ma perché preoccuparsi? Supponi solo che l'array sia del tipo che ti aspetti. In caso contrario, ti esploderà in faccia - questa è la programmazione dinamica per te! Metti alla prova il tuo codice e tutto andrà bene ...


1
Normalmente supponendo che l'array sia il tipo desiderato sarebbe la strada da percorrere. Ma nel mio caso sto eseguendo un ciclo attraverso un array multidimensionale e sto formattando l'output in base al tipo di array di un determinato nodo.
Wilco,

0

Metto a confronto la differenza tra le chiavi dell'array e le chiavi del risultato di array_values ​​() dell'array, che sarà sempre un array con indici interi. Se le chiavi sono uguali, non è un array associativo.

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

-1; questo utilizza O(n)memoria aggiuntiva quando $arraycontiene nelementi e scrivere (someboolean) ? false : trueinvece di !somebooleanè orribile e gratuito in modo dettagliato.
Mark Amery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.