web-dev-qa-db-fra.com

Comment vérifier si PHP tableau est associatif ou séquentiel?

PHP considère tous les tableaux comme étant associatifs, il n’ya donc aucune fonction intégrée. Quelqu'un peut-il recommander un moyen assez efficace de vérifier si un tableau ne contient que des clés numériques?

En gros, je veux pouvoir différencier ceci:

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

et ça:

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

Vous avez posé deux questions qui ne sont pas tout à fait équivalentes:

  • Tout d'abord, comment déterminer si un tableau n'a que des clés numériques
  • Deuxièmement, comment déterminer si un tableau a des clés numériques séquentielles, à partir de 0

Déterminez lequel de ces comportements dont vous avez réellement besoin. (Il se peut que l'un ou l'autre convienne à vos objectifs.)

La première question (vérifiant simplement que toutes les clés sont numériques) est bien répondu par le capitaine kurO .

Pour la deuxième question (vérifier si le tableau est à index zéro et séquentiel), vous pouvez utiliser la fonction suivante:

function isAssoc(array $arr)
{
    if (array() === $arr) return false;
    return array_keys($arr) !== range(0, count($arr) - 1);
}

var_dump(isAssoc(array('a', 'b', 'c'))); // false
var_dump(isAssoc(array("0" => 'a', "1" => 'b', "2" => 'c'))); // false
var_dump(isAssoc(array("1" => 'a', "0" => 'b', "2" => 'c'))); // true
var_dump(isAssoc(array("a" => 'a', "b" => 'b', "c" => 'c'))); // true
538
Greg

Pour vérifier simplement si le tableau a des clés non-entières (pas si le tableau est indexé séquentiellement ou indexé à zéro):

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

S'il existe au moins une clé de chaîne, $array sera considéré comme un tableau associatif.

409
Captain kurO

C’est sûrement une meilleure alternative.

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

De nombreux commentateurs dans cette question ne comprennent pas le fonctionnement des tableaux en PHP. À partir de documentation sur le tableau :

Une clé peut être un entier ou une chaîne. Si une clé est la représentation standard d’un entier, elle sera interprétée comme telle (c’est-à-dire que "8" sera interprété comme 8, tandis que "08" sera interprété comme "08"). Les flottants dans la clé sont tronqués en entier. Les types de tableaux indexés et associatifs sont du même type en PHP, ils peuvent contenir des index entiers et des index de chaînes.

En d'autres termes, une clé de tableau de "8" n'existe pas, car elle sera toujours (silencieusement) convertie en entier 8. Par conséquent, il est inutile d'essayer de différencier les entiers des chaînes numériques.

Si vous voulez le moyen le plus efficace de rechercher dans un tableau des clés non-entières sans copier une partie du tableau (comme le fait array_keys ()) ou la totalité de celui-ci (comme le fait 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);

Cela fonctionne car key () renvoie NULL lorsque la position actuelle du tableau n'est pas valide et que NULL ne peut jamais être une clé valide (si vous essayez d'utiliser NULL en tant que clé de tableau, elle est convertie silencieusement en "").

73
squirrel

Comme déclaré par le PO :

PHP traite tous les tableaux comme associatifs

il n'est pas très judicieux (IMHO) d'écrire une fonction qui vérifie si un tableau est associatif. Donc, la première chose à faire: Qu'est-ce qu'une clé dans un tableau PHP ?:

La touche peut être un entier ou une chaîne.

Cela signifie qu'il y a 3 cas possibles:

  • Cas 1. Toutes les clés sont numériques/entiers.
  • Cas 2. Toutes les clés sont strings.
  • Cas 3. Certaines clés sont strings, d'autres numériques/entiers.

Nous pouvons vérifier chaque cas avec les fonctions suivantes.

Cas 1: toutes les clés sont numériques/entiers.

Note: Cette fonction renvoie aussi true pour les tableaux vides.

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

Cas 2: toutes les clés sont strings.

Note: Cette fonction renvoie aussi true pour les tableaux vides.

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

Cas 3. Certaines clés sont strings, d'autres numériques/entiers.

Note: Cette fonction renvoie aussi true pour les tableaux vides.

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

Il s'ensuit que:


Maintenant, pour qu'un tableau soit un tableau "authentique" auquel nous sommes tous habitués, ce qui signifie:

  • Ses clés sont toutes numériques/entiers.
  • Ses touches sont séquentielles (c’est-à-dire croissant de l’étape 1).
  • Ses touches partent de zéro.

Nous pouvons vérifier avec la fonction suivante.

Cas 3a. les clés sont numérique/nombres entiers, séquentiel et basé sur zéro.

Note: Cette fonction renvoie aussi true pour les tableaux vides.

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

Mises en garde/pièges (ou des faits encore plus particuliers sur les clés de tableaux en PHP)

Clés entières

Les clés de ces tableaux sont entiers:

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

Clés de chaîne

Les clés pour ces tableaux sont chaînes:

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

Clés entières qui ressemblent à des chaînes

Si vous pensez que la clé dans array("13" => "b") est une chaîne , vous vous trompez. De la doc ici :

Les chaînes contenant des entiers valides seront converties dans le type entier. Par exemple. la clé "8" sera effectivement stockée sous 8. Par contre, "08" ne sera pas converti, car il ne s'agit pas d'un entier décimal valide.

Par exemple, la clé de ces tableaux est integers:

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

Mais la clé de ces tableaux est 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.

De plus, selon le doc ,

La taille d'un entier dépend de la plate-forme, bien qu'une valeur maximale d'environ deux milliards soit la valeur habituelle (32 bits signés). Les plates-formes 64 bits ont généralement une valeur maximale d'environ 9E18, à l'exception de Windows, qui est toujours 32 bits. PHP ne supporte pas les entiers non signés.

Donc, la clé pour ce tableau peut ou non être un entier - cela dépend de votre plate-forme.

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

Pire encore, PHP a tendance à être buggy si le nombre entier est proche de 231 = 2 147 483 648 limite (voir bogue 51430 , bogue 52899 ). Par exemple, dans mon environnement local (PHP 5.3.8 sous XAMPP 1.7.7 sous Windows 7), var_dump(array("2147483647" => "b")) donne

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

mais sur cette démonstration en direct sur le codepad (PHP 5.2.5), la même expression donne

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

Ainsi, la clé est un entier dans un environnement mais une chaîne dans un autre, même si 2147483647 est un entier 32 bits signé et valide.

37
Pang

Vitesse:

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

Mémoire sage:

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

Le moyen le plus efficace est donc:

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

Cela fonctionne car il compare les clés (qui pour un tableau séquentiel sont toujours 0,1,2, etc.) aux clés des clés (qui toujours sera 0,1,2, etc.).

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

J'ai utilisé à la fois array_keys($obj) !== range(0, count($obj) - 1) et array_values($arr) !== $arr (qui sont duels, bien que la seconde soit moins chère que la première), mais les deux échouent pour les très grands tableaux.

Ceci est dû au fait que array_keys et array_values sont des opérations très coûteuses (puisqu'elles construisent un tout nouveau tableau de taille à peu près équivalent à celui de l'original).

La fonction suivante est plus robuste que les méthodes fournies ci-dessus:

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

Notez également que si vous ne souhaitez pas différencier les tableaux fragmentés des tableaux associatifs, vous pouvez simplement renvoyer 'assoc' à partir des deux blocs if.

Enfin, bien que cela puisse paraître beaucoup moins "élégant" que beaucoup de "solutions" sur cette page, il est en pratique beaucoup plus efficace. Presque n'importe quel tableau associatif sera détecté instantanément. Seuls les tableaux indexés sont vérifiés de manière exhaustive, et les méthodes décrites ci-dessus non seulement vérifient les tableaux indexés de manière exhaustive, ils les dupliquent.

17
podperson

Je pense que les deux fonctions suivantes sont le meilleur moyen de vérifier "si un tableau est associatif ou numérique". Dans la mesure où "numérique" peut signifier uniquement des touches numériques ou uniquement des touches numériques séquentielles, deux fonctions répertoriées ci-dessous vérifient l'une ou l'autre condition:

function is_indexed_array(&$arr) {
  for (reset($arr); is_int(key($arr)); next($arr));
  return is_null(key($arr));
}

function is_sequential_array(&$arr, $base = 0) {
  for (reset($arr), $base = (int) $base; key($arr) === $base++; next($arr));
  return is_null(key($arr));
}

La première fonction vérifie si chaque clé est une valeur entière. La deuxième fonction vérifie si chaque clé est une valeur entière et vérifie en outre si toutes les clés sont séquentielles à partir de $ base, la valeur par défaut étant 0 et pouvant donc être omise si vous n'avez pas besoin de spécifier une autre valeur de base. key ($ my_array) renvoie la valeur null si le pointeur de lecture est déplacé au-delà de la fin du tableau, ce qui termine la boucle for et fait en sorte que l'instruction qui suit la boucle retourne true si toutes les clés sont des entiers. Sinon, la boucle se termine prématurément car une clé est de type chaîne et l'instruction qui suit la boucle for retournera false. Cette dernière fonction ajoute en outre un à $ base après chaque comparaison, afin de pouvoir vérifier si la clé suivante a la valeur correcte. La comparaison stricte permet également de vérifier si la clé est de type entier. La partie $ base = (int) $ base de la première section de la boucle for peut être omise lorsque $ base est omis ou si vous vous assurez qu'elle est uniquement appelée à l'aide d'un entier. Mais comme je ne peux pas en être sûr pour tout le monde, je l’ai laissé. La déclaration n’est exécutée qu’une fois, de toute façon. Je pense que ce sont les solutions les plus efficaces:

  • En ce qui concerne la mémoire: pas de copie de données ni de plages de clés. Faire un array_values ​​ou array_keys peut sembler plus court (moins de code), mais gardez à l'esprit ce qui se passe en arrière-plan une fois que vous avez passé cet appel. Oui, il y a plus d'énoncés (visibles) que dans d'autres solutions, mais ce n'est pas ce qui compte, n'est-ce pas?
  • En termes de temps: outre le fait que la copie/l'extraction de données et/ou de clés prend également du temps, cette solution est plus efficace que d'effectuer une opération foreach. Encore une fois, un foreach peut sembler plus efficace pour certains, car sa notation est plus courte, mais en arrière-plan, on appelle aussi reset, key et ensuite pour le faire en boucle. Mais en outre, il appelle également valid pour vérifier la condition de fin, ce qui est évité ici en raison de la combinaison avec la vérification de nombre entier.

N'oubliez pas qu'une clé de tableau ne peut être qu'un entier ou une chaîne, et une chaîne strictement numérique telle que "1" (mais pas "01") sera traduite en un entier. C’est ce qui fait que la vérification d’une clé entière est la seule opération nécessaire à part compter si vous souhaitez que le tableau soit séquentiel. Naturellement, si is_indexed_array retourne false, le tableau peut être vu comme étant associatif. Je dis «vu», car en fait ils sont tous.

13
Niels Ockeloen

J'ai remarqué deux approches populaires pour cette question: l'une utilisant array_values() et l'autre utilisant key(). Pour savoir lequel est le plus rapide, j’ai écrit un petit programme:

$arrays = Array(
  'Array #1' => Array(1, 2, 3, 54, 23, 212, 123, 1, 1),
  'Array #2' => Array("Stack", 1.5, 20, Array(3.4)),
  'Array #3' => Array(1 => 4, 2 => 2),
  'Array #4' => Array(3.0, "2", 3000, "Stack", 5 => "4"),
  'Array #5' => Array("3" => 4, "2" => 2),
  'Array #6' => Array("0" => "One", 1.0 => "Two", 2 => "Three"),
  'Array #7' => Array(3 => "asdf", 4 => "asdf"),
  'Array #8' => Array("Apple" => 1, "orange" => 2),
);

function is_indexed_array_1(Array &$arr) {
  return $arr === array_values($arr);
}

function is_indexed_array_2(Array &$arr) {
  for (reset($arr), $i = 0; key($arr) === $i++; next($arr))
    ;
  return is_null(key($arr));
}

// Method #1
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
  foreach ($arrays as $array) {
    $dummy = is_indexed_array_1($array);
  }
}
$end = microtime(true);
echo "Time taken with method #1 = ".round(($end-$start)*1000.0,3)."ms\n";

// Method #2
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
  foreach ($arrays as $array) {
    $dummy = is_indexed_array_2($array);
  }
}
$end = microtime(true);
echo "Time taken with method #1 = ".round(($end-$start)*1000.0,3)."ms\n";

La sortie du programme sur PHP 5.2 sur CentOS est la suivante:

Temps pris avec la méthode n ° 1 = 10.745ms
Temps pris avec la méthode n ° 2 = 18.239ms

La sortie sur PHP 5.3 a donné des résultats similaires. Évidemment, utiliser array_values() est beaucoup plus rapide.

7
Manu M.

Cette fonction peut gérer:

  • tableau avec des trous dans l'index (par exemple 1,2,4,5,8,10) 
  • tableau avec les clés "0x": par exemple la clé '08' est associative tandis que la clé '8' est séquentielle.

l'idée est simple: si l'une des clés n'est PAS un entier, il s'agit d'un tableau associatif, sinon il est séquentiel.

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

Il y a déjà beaucoup de réponses, mais voici la méthode sur laquelle Laravel s'appuie dans sa 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;
}

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

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

Rapide, concis et efficace en mémoire. Pas de comparaisons coûteuses, d'appels de fonction ou de copie de tableaux.

5
Jesse

Une façon de procéder consiste à utiliser le json_encode, qui possède déjà sa propre méthode interne de différenciation entre un tableau associatif et un tableau indexé afin de générer le bon JSON.

Vous pouvez le faire en vérifiant si le premier caractère renvoyé après le codage est un { (tableau associatif) ou un [ (tableau indexé).

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

En utilisant xarray PHP extension

Vous pouvez le faire très rapidement (environ 30 fois plus vite dans PHP 5.6): 

if (array_is_indexed($array)) {  }

Ou:

if (array_is_assoc($array)) {  }
4
c9s

Ma solution:

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

array_merge sur un tableau unique réindexera toutes les clés integer, mais pas les autres. Par exemple:

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

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

Ainsi, si une liste (un tableau non associatif) est créée ['a', 'b', 'c'], une valeur est supprimée unset($a[1]), puis array_merge est appelé, la liste est réindexée à partir de 0.

3
ByScripts

Voici la méthode que j'utilise:

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

Notez que cela ne tient pas compte des cas particuliers comme:

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

unset($a[1]);

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

Désolé, je ne peux pas vous aider avec ça. Il est également assez performant pour les tableaux de taille décente, car il ne fait pas de copies inutiles. Ce sont ces petites choses qui rendent Python et Ruby tellement plus agréables à écrire ...: P

2
AL the X

Je sais qu'il est un peu inutile d'ajouter une réponse à cette file d'attente énorme, mais voici une solution lisible O(n) qui ne nécessite aucune duplication de valeur:

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

Plutôt que de vérifier les clés pour voir si elles sont toutes numériques, vous parcourez les clés que serait présentes dans un tableau numérique et assurez-vous qu'elles existent.

2
cloudfeet

Je pense que la définition d'un tableau scalaire variera selon les applications. C'est-à-dire que certaines applications nécessiteront un sens plus strict de ce qui est qualifié de réseau scalaire, et que certaines applications nécessiteront un sens plus vague.

Ci-dessous, je présente 3 méthodes de sévérité variable.

<?php
/**
 * Since PHP stores all arrays as associative internally, there is no proper
 * definition of a scalar array.
 * 
 * As such, developers are likely to have varying definitions of scalar array,
 * based on their application needs.
 * 
 * In this file, I present 3 increasingly strict methods of determining if an
 * array is scalar.
 * 
 * @author David Farrell <[email protected]>
 */

/**
 * isArrayWithOnlyIntKeys defines a scalar array as containing
 * only integer keys.
 * 
 * If you are explicitly setting integer keys on an array, you
 * may need this function to determine scalar-ness.
 * 
 * @param array $a
 * @return boolean
 */ 
function isArrayWithOnlyIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    foreach ($a as $k => $v)
        if (!is_int($k))
            return false;
    return true;
}

/**
 * isArrayWithOnlyAscendingIntKeys defines a scalar array as
 * containing only integer keys in ascending (but not necessarily
 * sequential) order.
 * 
 * If you are performing pushes, pops, and unsets on your array,
 * you may need this function to determine scalar-ness.
 * 
 * @param array $a
 * @return boolean
 */ 
function isArrayWithOnlyAscendingIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    $prev = null;
    foreach ($a as $k => $v)
    {
        if (!is_int($k) || (null !== $prev && $k <= $prev))
            return false;
        $prev = $k;
    }
    return true;
}

/**
 * isArrayWithOnlyZeroBasedSequentialIntKeys defines a scalar array
 * as containing only integer keys in sequential, ascending order,
 * starting from 0.
 * 
 * If you are only performing operations on your array that are
 * guaranteed to either maintain consistent key values, or that
 * re-base the keys for consistency, then you can use this function.
 * 
 * @param array $a
 * @return boolean
 */
function isArrayWithOnlyZeroBasedSequentialIntKeys(array $a)
{
    if (!is_array($a))
        return false;
    $i = 0;
    foreach ($a as $k => $v)
        if ($i++ !== $k)
            return false;
    return true;
}
2
David Farrell

Cela fonctionnerait aussi ( demo ):

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

Veuillez noter que le but principal de cette réponse est de vous informer de l'existence de SplFixedArray et de ne pas vous encourager à utiliser Exceptions pour ce type de test.

2
Gordon

Cela pourrait-il être la solution?

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

La mise en garde est évidemment que le curseur du tableau est réinitialisé, mais je dirais que la fonction est probablement utilisée avant même que le tableau soit parcouru ou utilisé.

2
Kat Lim Ruiz
<?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);
}

?>

Ces deux exemples, qui ont obtenu le plus de points, ne fonctionnent pas correctement avec des tableaux comme $array = array('foo' => 'bar', 1)

2
KillEveryBody

Après quelques analyses locales, débogage, analyse du compilateur, profilage et utilisation abusive de 3v4l.org pour analyser plusieurs versions (oui, j’ai eu un avertissement à arrêter) et.

Je vous présente une fonction de test de tableau associatif dérivée de manière organique meilleure-moyenne-pire-scénario qui est à pire _ globalement aussi bonne ou meilleure que tous les autres scénarios de moyenne cas.

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

De 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";
}
1
TylerY86

Encore un rapide de source . Ajuster le codage de json_encode (et bson_encode). Il en va de même pour la conformité javascript à un tableau.

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
lazycommit

des réponses sont déjà données, mais la désinformation sur les performances est excessive ..__ J'ai écrit ce petit script de référence qui montre que la méthode foreach est la plus rapide.

Avertissement: les méthodes suivantes ont été copiées-collées à partir des autres réponses

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

résultats:

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

À mon avis, un tableau devrait être accepté comme associatif si l’une quelconque de ses clés n’est pas un entier, par ex. nombres flottants et chaîne vide ''.

De plus, les entiers non séquencés doivent être considérés comme associatifs comme (0,2,4,6) car ces types de tableaux ne peuvent pas être utilisés avec des boucles for de la manière suivante:

$n =count($arr);
for($i=0,$i<$n;$i++) 

La deuxième partie de la fonction ci-dessous vérifie si les clés sont indexées ou non. Cela fonctionne également pour les clés avec des valeurs négatives. Par exemple (-1,0,1,2,3,4,5)

count() = 7 , max = 5, min=-1



if( 7 == (5-(-1)+1 ) // true
    return false; // array not associative


/** 
 * isAssoc Checks if an array is associative
 * @param $arr reference to the array to be checked
 * @return bool 
 */     
function IsAssoc(&$arr){
    $keys= array_keys($arr);
    foreach($keys as $key){
        if (!is_integer($key))
            return true;
    }
    // if all keys are integer then check if they are indexed
    if(count($arr) == (max($keys)-min($keys)+1))
        return false;
    else
        return true;
}
0
Selim Acar

La plupart des solutions proposées ici sont élégantes et jolies, mais ne s’échelonnent pas très bien et exigent beaucoup de mémoire ou de CPU. La plupart créent 2 nouveaux points de données en mémoire avec cette solution des deux côtés de la comparaison. Plus la matrice est large, plus le processus et la mémoire utilisés sont durs et longs, et vous perdez l'avantage de l'évaluation des courts-circuits. J'ai fait des essais avec quelques idées différentes. Essayez d'éviter array_key_exists car cela coûte cher, et évitez également de créer de grands ensembles de données à comparer. Je pense que c'est un moyen simple de savoir si un tableau est séquentiel.

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

Vous exécutez un seul compte sur le tableau principal et stockez un seul entier. Vous parcourez ensuite le tableau et recherchez une correspondance exacte lors de l'itération du compteur. Vous devriez avoir de 1 à compter. En cas d'échec, un court-circuit s'ensuivra, ce qui vous donnera un gain de performance lorsqu'il sera faux.

A l'origine, je l'ai fait avec une boucle for et une vérification de isset ($ arr [$ i]), mais cela ne détectera pas les clés nulles qui nécessitent array_key_exists, et comme nous le savons, c'est la pire fonction à utiliser pour la rapidité.

Mise à jour constante des variables via foreach pour vérifier avec l'itérateur que sa taille ne dépasse jamais la taille de son entier, utilisons PHP pour optimiser la mémoire, la mise en cache et le garbage collection afin de vous garder avec une utilisation très faible des ressources.

En outre, je ferai valoir que l'utilisation de array_keys dans un foreach est idiot quand vous pouvez simplement exécuter $ key => $ value et vérifier la clé. Pourquoi créer le nouveau point de données? Une fois que vous avez retiré les clés de la matrice, vous avez immédiatement utilisé plus de mémoire.

0
geilt

Je suis venu avec la méthode suivante:

function isSequential(array $list): bool
{
    $i = 0;
    $count = count($list);
    while (array_key_exists($i, $list)) {
        $i += 1;
        if ($i === $count) {
            return true;
        }
    }

    return false;
}


var_dump(isSequential(array())); // false
var_dump(isSequential(array('a', 'b', 'c'))); // true
var_dump(isSequential(array("0" => 'a', "1" => 'b', "2" => 'c'))); // true
var_dump(isSequential(array("1" => 'a', "0" => 'b', "2" => 'c'))); // true
var_dump(isSequential(array("1a" => 'a', "0b" => 'b', "2c" => 'c'))); // false
var_dump(isSequential(array("a" => 'a', "b" => 'b', "c" => 'c'))); // false

* Remarque: un tableau vide n'est pas considéré comme un tableau séquentiel, mais je pense que c'est bien, car les tableaux vides sont comme 0 - peu importe que ce soit plus ou moins, c'est vide.

Voici les avantages de cette méthode par rapport aux méthodes énumérées ci-dessus:

  • Il ne s'agit pas de copier des tableaux (une personne mentionnée dans ce Gist https://Gist.github.com/Thinkscape/1965669 que array_values n'implique pas de copier - quoi! ?? C'est certainement le cas - comme ce sera le cas vu ci-dessous)
  • C'est plus rapide pour les baies plus grandes et pour plus de mémoire en même temps

J'ai utilisé des repères aimablement fournis par Artur Bodera , où j'ai changé l'un des tableaux en éléments 1M (array_fill(0, 1000000, uniqid()), // big numeric array).

Voici les résultats pour 100 itérations:

PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )

Initial memory: 32.42 MB
Testing my_method (isset check) - 100 iterations
  Total time: 2.57942 s
  Total memory: 32.48 MB

Testing method3 (array_filter of keys) - 100 iterations
  Total time: 5.10964 s
  Total memory: 64.42 MB

Testing method1 (array_values check) - 100 iterations
  Total time: 3.07591 s
  Total memory: 64.42 MB

Testing method2 (array_keys comparison) - 100 iterations
  Total time: 5.62937 s
  Total memory: 96.43 MB

* Les méthodes sont commandées en fonction de leur consommation de mémoire

** J'ai utilisé echo " Total memory: " . number_format(memory_get_peak_usage()/1024/1024, 2) . " MB\n"; pour afficher l'utilisation de la mémoire

0
Slayer Birden
function is_array_assoc($foo) {
    if (is_array($foo)) {
        return (count(array_filter(array_keys($foo), 'is_string')) > 0);
    }
    return false;
}
0
macki

Je compare la différence entre les clés du tableau et les clés du résultat de array_values ​​() du tableau, qui sera toujours un tableau avec des indices entiers. Si les clés sont les mêmes, ce n'est pas un tableau associatif.

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

À moins que PHP ne soit intégré à cela, vous ne pourrez pas le faire en moins de O(n) - énumérant toutes les clés et vérifiant le type entier. En fait, vous voulez également vous assurer qu'il n'y a pas de trous afin que votre algorithme ressemble à ceci:

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

Mais pourquoi s'embêter? Supposons simplement que le tableau est du type que vous attendez. Si ce n'est pas le cas, cela va exploser dans votre visage - c'est de la programmation dynamique pour vous! Testez votre code et tout ira bien ...

0
Daren Thomas
function is_associative($arr) {
  return (array_merge($arr) !== $arr || count(array_filter($arr, 'is_string', ARRAY_FILTER_USE_KEY)) > 0);
}
0
scronide

Encore une autre façon de faire cela.

function array_isassociative($array)
{
    // Create new Array,  Make it the same size as the input array
    $compareArray = array_pad(array(), count($array), 0);

    // Compare the two array_keys
    return (count(array_diff_key($array, $compareArray))) ? true : false;

}
0
Mez
/*
iszba - Is Zero Based Array

Detects if an array is zero based or not.

PARAMS:
    $chkvfnc
        Callback in the loop allows to check the values of each element.
        Signature:
            bool function chkvfnc($v);
            return:
                true    continue looping
                false   stop looping; iszba returns false too.

NOTES:
○ assert: $array is an array.
○ May be memory efficient;
  it doesn't get extra arrays via array_keys() or ranges() into the function.
○ Is pretty fast without a callback.
○ With callback it's ~2.4 times slower.
*/
function iszba($array, $chkvfnc=null){

    $ncb = !$chkvfnc;
    $i = 0;

    foreach($array as $k => $v){
        if($k === $i++)
            if($ncb || $chkvfnc($v))
                continue;

        return false;
    }

    return true;
}

• Sans rappel, il est environ 30% plus rapide que la réponse principale actuelle, Et peut-être plus efficace en mémoire.

• Annulez simplement la réponse pour savoir si le tableau doit être considéré comme associatif.

0
rbgo

Ou vous pouvez simplement utiliser ceci:

Arr::isAssoc($array)

qui vérifiera si le tableau contient toute clé non numérique ou:

Arr:isAssoc($array, true)

vérifier si le tableau est strictement séquentiel (contient les clés int générées automatiquement 0 à n-1)

en utilisant this library.

0
Minwork

Modification sur la réponse la plus populaire.
Cela prend un peu plus de temps, mais est plus précis. 

<?php
//$a is a subset of $b
function isSubset($a, $b)
{
    foreach($a =>$v)
        if(array_search($v, $b) === false)
            return false;

    return true;

    //less effecient, clearer implementation. (uses === for comparison)
    //return array_intersect($a, $b) === $a;
}

function isAssoc($arr)
{
    return !isSubset(array_keys($arr), range(0, count($arr) - 1));
}

var_dump(isAssoc(array('a', 'b', 'c'))); // false
var_dump(isAssoc(array(1 => 'a', 0 => 'b', 2 => 'c'))); // false
var_dump(isAssoc(array("0" => 'a', "1" => 'b', "2" => 'c'))); // false 
//(use === in isSubset to get 'true' for above statement)
var_dump(isAssoc(array("a" => 'a', "b" => 'b', "c" => 'c'))); // true
?>
0
Jason McCarrell