Как проверить, является ли массив PHP ассоциативным или последовательным?

781

PHP обрабатывает все массивы как ассоциативные, поэтому встроенных функций нет. Кто-нибудь может порекомендовать довольно эффективный способ проверить, содержит ли массив только числовые ключи?

В принципе, я хочу иметь возможность различать это:

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

и это:

$assocArray = array('fruit1' => 'apple', 
                    'fruit2' => 'orange', 
                    'veg1' => 'tomato', 
                    'veg2' => 'carrot');
Wilco
источник
382
В вашем коде есть ошибка: помидор - это фрукт.
Олле Харстедт
9
У этого метода есть предостережения, но часто я просто делаю if (isset($array[0])), что просто и быстро. Конечно, вы должны сначала убедиться, что массив не пустой, и у вас должны быть некоторые знания о возможном содержимом массива, чтобы метод не мог завершиться с ошибкой (например, смешанный числовой / ассоциативный или непоследовательный).
Двойной Гра
@ OlleHärstedt Не по мнению Высокого суда США. ;-)
MC Emperor

Ответы:

622

Вы задали два вопроса, которые не совсем эквивалентны:

  • Во-первых, как определить, имеет ли массив только цифровые ключи
  • Во-вторых, как определить, имеет ли массив последовательные числовые ключи, начиная с 0

Подумайте, какое из этих поведений вам действительно нужно. (Может быть, что либо подойдет для ваших целей.)

На первый вопрос (просто проверяя, что все ключи цифровые) хорошо ответил Captain kurO .

Для второго вопроса (проверка, является ли массив нулевым и последовательным), вы можете использовать следующую функцию:

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
Очень элегантное решение. Обратите внимание, что он возвращает TRUE в (неоднозначном) случае пустого массива.
Джонатан Лидбек
30
Я думаю, что более полезно думать о последовательных массивах как об особом случае ассоциативных массивов. Таким образом, каждый массив является ассоциативным, но только некоторые являются последовательными. Следовательно, функция isSequential()будет иметь больше смысла, чем isAssoc(). В такой функции пустой массив должен рассматриваться как последовательный. Формула может быть array() === $arr || !isAssoc($arr).
Donquixote
18
Я думаю, что это позволило бы избежать большого количества потенциального процессорного времени и памяти, если бы можно было проверить, является ли isset ($ arr [0]) ложным, прежде чем извлекать все ключи, как это явно ассоциативно, если массив не пустой, но не имеет элемента в 0 должность. Поскольку «большинство» реальных ассоциативных массивов имеют строки в качестве ключей, это должно быть хорошей оптимизацией для общего случая такой функции.
OderWat
10
@OderWat - Ваша оптимизация должна использовать array_key_existsвместо того, чтобы, issetесли нулевой элемент является нулевым значением, isset вернет false неправильно. Нулевое значение обычно должно быть допустимым значением в таком массиве.
OCDev
@MAChitgarha, ваше изменение изменило поведение функции без объяснения причин и сделало его противоречащим описанию в прозе выше того, что она на самом деле должна делать. Я вернул это.
Марк Амери
431

Чтобы просто проверить, есть ли в массиве нецелочисленные ключи (не является ли массив последовательно индексированным или нулевым):

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

Если есть хотя бы один строковый ключ, $arrayбудет рассматриваться как ассоциативный массив.

Captain kurO
источник
22
Этот метод намного лучше, чем кажется. Если count (filter_array) == count (original_array), то это массив ассоциаций. Если count (Filter_array) == 0, то это индексированный массив. Если count (filter_array) <count (original_array), то массив имеет как числовые, так и строковые ключи.
Джамол
5
@MikePretzlaw конечно, это повторяется; (очевидно) нет никакого способа определить, являются ли все ключи массива целыми, не просматривая все ключи в массиве. Я предполагаю, что не повторяющиеся альтернативы, которые мы должны увидеть ниже, похожи $isIndexed = array_values($arr) === $arr;? На что я спрашиваю: как вы думаете, array_values()работает? Как вы думаете, ===применительно к массивам работает? Ответ, конечно, в том, что они также перебирают массив.
Марк Эмери
4
@ARW "PHP, по-видимому, преобразует все в int в определении массива, если может." - Да, это именно то, что происходит. Самым большим WTF является то, что он даже делает это с плавающей точкой; если вы попробуете, var_dump([1.2 => 'foo', 1.5 => 'bar']);вы обнаружите, что вы получите массив [1 => 'bar']. Нет никакого способа узнать оригинальный тип ключа. Да, все это ужасно; Массивы PHP на сегодняшний день являются худшей частью языка, и большая часть ущерба является непоправимой и обусловлена ​​идеей использования единой конструкции для традиционных массивов и традиционных хеш-карт с самого начала.
Марк Эмери
30
@MarkAmery Вышеуказанное, хотя и простое, гарантирует 100% обход массива. Это было бы более эффективно, особенно если вы имеете дело с большими массивами, если вы проверяли на string или int и выявлялись при первом обнаружении. Например: function isAssociative($arr) { foreach ($arr as $key => $value) { if (is_string($key)) return true; } return false; }
Мысль
1
@ Thought Ваш код работает очень быстро, но он не может обнаружить последовательный массив . Примером array(1 => 'a', 0 => 'b', 2 => 'c')станет false(последовательный массив), а должно быть true(ассоциативный массив). toolsqa.com/data-structures/array-in-programming Я не уверен, что ключ должен быть в порядке возрастания? (0, 1, ...)
ви
132

Конечно, это лучшая альтернатива.

<?php
$arr = array(1,2,3,4);
$isIndexed = array_values($arr) === $arr;
Dave Marshall
источник
52
Это дублирует значения в массиве, что потенциально очень дорого. Вам гораздо лучше исследовать ключи массива.
Meagar
8
Я просто использовал ==; Я не думаю, что здесь есть необходимость ===. Но, отвечая на вопрос «unset, и он не работает»: после того, как вы сбросили первый элемент, он больше не является целочисленным индексированным массивом, начинающимся с 0. Таким образом, IMO работает.
grantwparks
4
Согласитесь с @grantwparks: разреженный массив не индексируется. Интересно, что на самом деле нет способа удалить элемент из середины индексированного массива. PHP в основном объявляет все массивы как ассоциативные, а числовые - просто версия «сделай ключ для меня».
RickMeasham
7
Единственная проблема, с которой я столкнулся, заключается в том, что мы ===будем тратить время на проверку, равны ли значения, хотя нас интересуют только ключи. По этой причине я предпочитаю $k = array_keys( $arr ); return $k === array_keys( $k );версию.
Джесси
5
Добавленное примечание, это терпит неудачу на массивах, указанных с числовыми ключами, которые не в порядке. например, $ myArr = array (0 => 'a', 3 => 'b', 4 => 1, 2 => 2, 1 => '3'); Один потенциальный обходной путь - запустить ksort ($ arr) перед выполнением теста
Скотт,
77

Многие комментаторы в этом вопросе не понимают, как работают массивы в PHP. Из документации массива :

Ключ может быть целым числом или строкой. Если ключ является стандартным представлением целого числа, оно будет интерпретировано как таковое (т.е. «8» будет интерпретировано как 8, а «08» будет интерпретировано как «08»). Поплавки в ключе усекаются до целого числа. Индексированные и ассоциативные типы массивов - это один и тот же тип в PHP, который может содержать как целые, так и строковые индексы.

Другими словами, не существует такой вещи, как ключ массива «8», потому что он всегда будет (молча) преобразовываться в целое число 8. Поэтому пытаться различать целые и числовые строки не нужно.

Если вы хотите наиболее эффективный способ проверить массив на наличие нецелочисленных ключей без создания копии части массива (как это делает array_keys ()) или всего этого (как и 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);

Это работает, потому что key () возвращает NULL, когда текущая позиция массива недопустима, и NULL никогда не может быть допустимым ключом (если вы попытаетесь использовать NULL в качестве ключа массива, он будет автоматически преобразован в "").

белка
источник
Это не работает для непоследовательных целочисленных ключей. Попробуйте это с [2 => 'a', 4 => 'b'].
DavidJ
2
@DavidJ, что вы подразумеваете под "не работает"? Он успешно определяет, что все ключи являются целыми числами. Вы утверждаете, что массив, подобный тому, который вы разместили, не должен рассматриваться как «числовой массив»?
coredumperror
7
Неассоциативный массив должен иметь ключи в диапазоне от 0до count($array)-1в этом строгом порядке. Предварительная проверка с is_array()может помочь. Добавьте увеличивающуюся переменную, чтобы проверить последовательность клавиш: for ($k = 0, reset($array) ; $k === key($array) ; next($array)) ++$k;это урегулирует сделку.
ofavre
2
Использование foreachвместо явной итерации примерно в два раза быстрее.
ofavre
1
Если вы хотите превратить это в функцию: function isAssocStr($array) { for (reset($array); is_int(key($array)); next($array)) { if (is_null(key($array))) return false; } return true; }
GreeKatrina
39

Как указано в ОП :

PHP рассматривает все массивы как ассоциативные

не совсем разумно (ИМХО) писать функцию, которая проверяет, является ли массив ассоциативным . Итак, первым делом: что является ключом в массиве PHP ?

Ключ может быть либо целым числом или строкой .

Это означает, что есть 3 возможных случая:

  • Случай 1. все ключи числовые / целые .
  • Случай 2. все ключи являются строками .
  • Случай 3. некоторые ключи являются строками , некоторые ключи являются числовыми / целыми числами .

Мы можем проверить каждый случай с помощью следующих функций.

Случай 1: все ключи числовые / целые .

Примечание : эта функция возвращает true и для пустых массивов.

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

Случай 2: все ключи являются строками .

Примечание : эта функция возвращает true и для пустых массивов.

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

Случай 3. некоторые ключи являются строками , некоторые ключи являются числовыми / целыми числами .

Примечание : эта функция возвращает true и для пустых массивов.

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

Это следует из того:


Теперь, чтобы массив был «подлинным» массивом, к которому мы все привыкли, это означает:

  • Его ключи все числовые / целые числа .
  • Его ключи являются последовательными (т.е. увеличиваются с шагом 1).
  • Его ключи начинаются с нуля .

Мы можем проверить с помощью следующей функции.

Дело 3а. ключи числовые / целые , последовательные и начинающиеся с нуля .

Примечание : эта функция возвращает true и для пустых массивов.

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

Предостережения / Подводные камни (или, еще более странные факты о ключах массива в PHP)

Целочисленные ключи

Ключи для этих массивов являются целыми числами :

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

Струнные ключи

Ключи для этих массивов являются строками :

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!

Целочисленные ключи, которые выглядят как строки

Если вы думаете, что ключ array("13" => "b")является строкой , вы ошибаетесь . Из документа здесь :

Строки, содержащие действительные целые числа, будут преобразованы в целочисленный тип. Например, ключ «8» будет фактически сохранен в 8. С другой стороны, «08» не будет приведен, поскольку он не является действительным десятичным целым числом.

Например, ключ для этих массивов - целые числа :

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

Но ключом для этих массивов являются строки :

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.

Более того, согласно документу ,

Размер целого числа зависит от платформы, хотя максимальное значение около двух миллиардов является обычным значением (это 32 бита со знаком). Максимальное значение для 64-разрядных платформ обычно составляет около 9E18, за исключением Windows, которая всегда является 32-разрядной. PHP не поддерживает целые числа без знака.

Таким образом, ключ для этого массива может быть или не быть целым числом - это зависит от вашей платформы.

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

Хуже того, PHP склонен к ошибкам, если целое число находится рядом с границей 2 31 = 2 147 483 648 (см. Ошибку 51430 , ошибку 52899 ). Например, в моей локальной среде (PHP 5.3.8 на XAMPP 1.7.7 на Windows 7), var_dump(array("2147483647" => "b"))дает

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

но в этом живом демо на кодовой панели (PHP 5.2.5) то же выражение дает

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

Таким образом, ключ - это целое число в одной среде, но строка в другой, даже если 2147483647это действительное 32-разрядное целое число со знаком .

Пан
источник
2
За исключением того, что, как я упоминаю ниже, это включает создание дублирующего массива для проверяемого массива, что делает его очень дорогим для больших массивов и потенциальным источником сбоя нехватки памяти на общих хостах.
podperson
35

Скорость-накрест:

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

Память-накрест:

function isAssoc($array)
{
    $array = array_keys($array); return ($array !== array_keys($array));
}
Аликс Аксель
источник
следующий массив: массив (02 => 11,1,2,456); показано, что не имеет числовых ключей с использованием вышеуказанного алгоритма, даже если 02 === 2
Galileo_Galilei
20
function checkAssoc($array){
    return  ctype_digit( implode('', array_keys($array) ) );
}
DSIMS
источник
2
Это единственный ответ (на момент моего комментария), который может касаться следующего: $ array = array (0 => 'blah', 2 => 'yep', 3 => 'wahey')
Shabbyrobe
но array('1'=>'asdf', '2'=>'too')будет рассматриваться как ассоциативный массив, хотя на самом деле это не так (ключи на самом деле строковые)
Captain kurO
1
@CaptainkurO Вы имеете в виду числовой. Это ассоциативный массив.
devios1
1
Эта функция возвращает, trueесли ключи: ноль, целые числа (только положительные), пустая строка или любая комбинация вышеперечисленного, например строка «09». Эта функция не учитывает порядок ключей. Значит array(0=>'blah', 2=>'yep', 3=>'wahey'), array(0=>'blah', 2=>'yep', 1=>'wahey')и array('blah', 'yep', 'wahey')все ассоциативны по этой функции, пока array('a'=>'blah', 'b'=>'yep', 'c'=>'wahey')нет.
Пан
@CaptainkurO вы не правы. «1» и «2» будут храниться как целые числа. Прочитайте цитируемую часть ответа белки от 11 мая 2011 года в 19:34. PHP не хранит строковые ключи, которые выглядят в точности как целые числа. Он конвертирует их в целые числа.
Баттл Буткус
20

На самом деле самый эффективный способ это:

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

Это работает, потому что сравнивает ключи (которые для последовательного массива всегда 0,1,2 и т. Д.) С ключами ключей (которые всегда будут 0,1,2 и т. Д.).

скоро
источник
1
Умно, но не хорошо. Почему это «самый эффективный»? Было бы намного удобнее читать, просто сравнив array_keys ($ a) с диапазоном (0, count ($ a)). Самое умное решение редко бывает лучшим в моем опыте. Особенно, когда умный добавляет буквально никакой ценности по сравнению с очевидной и чистой альтернативой.
Шейн Х,
4
Эта функция возвращает trueдля, array(1=>"a")но falseдля array("a"=>"a"). Было бы более значимым, если !=заменить на !==.
Пан
1
@ Блин, ты прав. Я думал, что ваш комментарий сначала должен быть неверным, но, к моему удивлению, [0] == ['a']в PHP (поскольку 0 == 'a', и действительно 0 == 'banana'). ==Оператор PHP безумен.
Марк Амери
2
Это неэффективно, поскольку включает вызов array_keys вместо простой проверки, пока вы не найдете непоследовательный целочисленный индекс. В любом случае , вы делаете это под капотом , но вы уже продублировали большой массив.
podperson
17

Я использовал оба array_keys($obj) !== range(0, count($obj) - 1)и array_values($arr) !== $arr(которые являются двойными по отношению друг к другу, хотя второе дешевле первого), но оба не подходят для очень больших массивов.

Это происходит потому , что array_keysи array_valuesоба очень дорогостоящие операции (так как они создают совершенно новый массив размером примерно с оригиналом).

Следующая функция более надежна, чем методы, представленные выше:

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

Также обратите внимание, что если вы не хотите отличать разреженные массивы от ассоциативных, вы можете просто вернуться 'assoc'из обоих ifблоков.

Наконец, хотя это может показаться гораздо менее «изящным», чем многие «решения» на этой странице, на практике это гораздо более эффективно. Почти любой ассоциативный массив будет обнаружен мгновенно. Только индексированные массивы будут проверяться исчерпывающе, а описанные выше методы не только тщательно проверяют индексированные массивы, но и дублируют их.

podperson
источник
13

Я думаю, что следующие две функции - лучший способ проверить, является ли массив ассоциативным или числовым. Поскольку «числовой» может означать только цифровые клавиши или только последовательные цифровые клавиши, ниже перечислены две функции, которые проверяют любое условие:

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

Первая функция проверяет, является ли каждая клавиша целочисленным значением. Вторая функция проверяет, является ли каждая клавиша целочисленным значением, и дополнительно проверяет, все ли ключи последовательны, начиная с $ base, который по умолчанию равен 0 и, следовательно, может быть опущен, если вам не нужно указывать другое базовое значение. key ($ my_array) возвращает значение null, если указатель чтения перемещается за конец массива, что и завершает цикл for, а оператор после цикла for возвращает true, если все ключи были целыми числами. Если нет, цикл преждевременно завершается, потому что ключ имеет тип string, а оператор после цикла for возвращает false. Последняя функция дополнительно добавляет единицу к $ base после каждого сравнения, чтобы иметь возможность проверить, имеет ли следующий ключ правильное значение. Строгое сравнение позволяет также проверить, имеет ли ключ тип integer. Часть $ base = (int) $ base в первом разделе цикла for может быть пропущена, если $ base пропущен или если вы убедитесь, что он вызывается только с помощью целого числа. Но так как я не могу быть уверен во всех, я оставил это. В любом случае, заявление выполняется только один раз. Я думаю, что это самые эффективные решения:

  • Память мудрая: нет копирования данных или ключевых диапазонов. Выполнение array_values ​​или array_keys может показаться более коротким (меньше кода), но имейте в виду, что происходит в фоновом режиме после выполнения этого вызова. Да, есть больше (видимых) утверждений, чем в некоторых других решениях, но это не главное, не так ли?
  • Время: Помимо того, что копирование / извлечение данных и / или ключей также требует времени, это решение более эффективно, чем выполнение foreach. Опять же, foreach может показаться более эффективным для некоторых, потому что он короче в нотации, но в фоновом режиме foreach также вызывает сброс, нажатие клавиши, и рядом с ним выполняется цикл. Но кроме того, он также вызывает valid для проверки конечного условия, которого здесь избегают благодаря комбинации с целочисленной проверкой.

Помните, что ключ массива может быть только целым числом или строкой, и строго числовая строка, такая как «1» (но не «01»), будет преобразована в целое число. Что делает проверку целочисленного ключа единственной необходимой операцией, кроме подсчета, если вы хотите, чтобы массив был последовательным. Естественно, если is_indexed_array возвращает false, массив можно рассматривать как ассоциативный. Я говорю «видел», потому что на самом деле они все есть.

Нильс Окелоен
источник
1
Это лучший ответ. Определение «ассоциативного» или «числового» массива зависит от конкретной ситуации.
Пато
Если foreach менее эффективен, чем метод, используемый здесь, тогда, кроме неудобства использования двух разных функций, производительность этого решения лучше, чем у меня (предыдущая). Я подозреваю, что это не так, поскольку foreach рекомендуется как самый быстрый способ прохождения массива.
podperson
7

Эта функция может обрабатывать:

  • массив с отверстиями в индексе (например, 1,2,4,5,8,10)
  • массив с ключами «0x»: например, ключ «08» является ассоциативным, а ключ «8» - последовательным.

Идея проста: если один из ключей НЕ является целым числом, это ассоциативный массив, в противном случае он является последовательным.

function is_asso($a){
    foreach(array_keys($a) as $key) {if (!is_int($key)) return TRUE;}
    return FALSE;
}
оборота Лазнико
источник
1
«если один из ключей НЕ является целым числом, это ассоциативный массив, в противном случае он является последовательным» - а? Нет, это просто неправильно. Есть место для споров о том, что представляет собой «ассоциативный» массив, но значение «последовательный» довольно однозначно, и это не то же самое, что все ключи являются числами.
Марк Амери
Если один из ключей НЕ является целым числом, он по своей природе является ассоциативным, однако он будет только последовательным, если ключи идут от 0 - длина (массив) - 1. Однако он ЦИФРОВОЙ, если все ключи только пронумерованы, но может или может не работать со многими функциями массива, которые требуют последовательного массива. Если вы преобразуете числовой массив с отверстиями в последовательный, запустив на нем array_values ​​(array), он будет преобразован в последовательный.
geilt
7

Я заметил два популярных подхода к этому вопросу: один использует, array_values()а другой использует key(). Чтобы выяснить, что быстрее, я написал небольшую программу:

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

Вывод для программы на PHP 5.2 на CentOS выглядит следующим образом:

Время, затраченное на метод № 1 = 10,745 мс
Время, затраченное на метод № 2 = 18,239 мс

Вывод на PHP 5.3 дал аналогичные результаты. Очевидно, что использование array_values()намного быстрее.

Ману Манджунатх
источник
плохой тест Вы не проверяли большие массивы. На моем компьютере, начиная с 10K + элементов, метод № 2 быстрее. Попробуйте с$arrays = Array( 'Array #1' => range(0, 50000), );
нонсенс
7

Одним из способов решения этой проблемы является использование контекста json_encode, который уже имеет свой собственный внутренний метод различения ассоциативного массива и индексированного массива для вывода правильного JSON.

Вы можете сделать это, проверив, является ли первый символ, возвращаемый после кодирования, {(ассоциативный массив) или [(индексированный массив).

// Too short :)
function is_assoc($arr) {
    ksort($arr);
    return json_encode($arr)[0] === '{';
}
MAChitgarha
источник
На мой взгляд, ksort () не нужен. Это решение работает, но оно должно проверить, имеет ли $ arr значение null и не работает ли json_encode, так что попытайтесь / поймайте. + это не совсем оптимально, если $ arr велико.
lucbonnin
7

Уже есть много ответов, но вот метод, на который опирается Laravel в своем классе 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;
}

Источник: https://github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Arr.php

Ben
источник
1
@Casey array_keys($keys)вернет последовательный массив чисел (0 ... X), который имеет ту же длину исходного массива. Например array_keys(["a", "b", "c"]) = [0, 1, 2]; array_keys([0, 1, 2]) = [0, 1, 2](это последовательный массив, потому что [0, 1, 2] !== [0, 1, 2]). Другой пример: array_keys(["a" => 5, "b" => 7, "c" => 10]) = ["a", "b", "c"]; array_keys(["a", "b", "c"]) = [0, 1, 2](это ассоциативный массив, потому что ["a", "b", "c"] !== [0, 1, 2]). Надеюсь, это понятно (сложно объяснить в комментариях, по крайней мере, для меня)
valepu
Этот алгоритм сумасшедший, легкий, понятный.
Benyi
Это не будет работать, если у вас есть последовательный массив ассоциативных строк.
lucbonnin
5
function array_is_assoc(array $a) {
    $i = 0;
    foreach ($a as $k => $v) {
        if ($k !== $i++) {
            return true;
        }
    }
    return false;
}

Быстро, лаконично и эффективно работает с памятью. Никаких дорогих сравнений, вызовов функций или копирования массивов.

Джесси
источник
4

Используя расширение xarray PHP

Вы можете сделать это очень быстро (примерно в 30+ раз быстрее в PHP 5.6):

if (array_is_indexed($array)) {  }

Или:

if (array_is_assoc($array)) {  }
оборота с9с
источник
3

Я знаю, что немного бессмысленно добавлять ответ в эту огромную очередь, но вот читаемое решение O (n), которое не требует дублирования каких-либо значений:

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

Вместо того, чтобы проверять ключи, чтобы увидеть, все ли они числовые, вы перебираете ключи, которые будут там для числового массива, и убедитесь, что они существуют.

cloudfeet
источник
еще один момент. массив в форме [1,2,null,4]потерпит неудачу, но это правильный массив. поэтому я добавил некоторые улучшения в stackoverflow.com/a/25206156/501831 с array_key_existsпроверкой на добавление )
lazycommit
-1; isset()это неправильный инструмент, потому что он вернет false, если значение установлено, но null, как указано @lazycommit.
Марк Амери
3

Мое решение:

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

array_mergeв одном массиве будут переиндексированы все integerключи, но не другие. Например:

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

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

Таким образом, если создается список (неассоциативный массив), ['a', 'b', 'c']то значение удаляется, а unset($a[1])затем array_mergeвызывается, список переиндексируется, начиная с 0.

ByScripts
источник
-1; это используется O(n)в дополнительной памяти (так как он создал несколько новых массивов с таким количеством элементов $array), ответ не решает неоднозначность задаваемого вопроса и не объясняет, как именно он определяет список / неассоциативный массив, и даже если ни один из этих пунктов не был правдой, неясно, добавляет ли это какую-либо ценность по сравнению с другими уже опубликованными ответами.
Марк Амери
3

После некоторого локального бенчмаркинга, отладки, проверки компилятора, профилирования и злоупотребления 3v4l.org для бенчмаркинга на нескольких версиях (да, я получил предупреждение об остановке) и сравнения со всеми вариантами, которые я смог найти ...

Я даю вам органически полученную тестовую функцию ассоциативного массива сценария с наилучшим средним и наихудшим случаем, которая в худшем случае примерно так же хороша или лучше, чем все остальные сценарии среднего случая.

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

С https://3v4l.org/rkieX :

<?php

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

// method_2 was DQ; did not actually work

// array_keys
function method_3(Array &$arr) {
    return array_keys($arr) === range(0, count($arr) - 1);
}

// foreach
function method_4(Array &$arr) {
    $idx = 0;
    foreach( $arr as $key => $val ){
        if( $key !== $idx )
            return FALSE;
        ++$idx;
    }
    return TRUE;
}

// guessing
function method_5(Array &$arr) {
    global $METHOD_5_KEY;
    $i = 0;
    $l = count($arr)-1;

    end($arr);
    if ( key($arr) !== $l )
        return FALSE;

    reset($arr);
    do {
        if ( $i !== key($arr) )
            return FALSE;
        ++$i;
        next($arr);
    } while ($i < $l);
    return TRUE;
}

// naieve
function method_6(Array &$arr) {
    $i = 0;
    $l = count($arr);
    do {
        if ( NULL === @$arr[$i] )
            return FALSE;
        ++$i;
    } while ($i < $l);
    return TRUE;
}

// deep reference reliance
function method_7(Array &$arr) {
    return array_keys(array_values($arr)) === array_keys($arr);
}


// organic (guessing + array_values)
function method_8(Array &$arr) {
    reset($arr);
    if ( key($arr) !== 0 )
        return FALSE;

    end($arr);
    if ( key($arr) !== count($arr)-1 )
        return FALSE;

    return array_values($arr) === $arr;
}

function benchmark(Array &$methods, Array &$target, $expected){    
    foreach($methods as $method){
        $start = microtime(true);
        for ($i = 0; $i < 2000; ++$i) {
            //$dummy = call_user_func($method, $target);
            if ( $method($target) !== $expected ) {
                echo "Method $method is disqualified for returning an incorrect result.\n";
                unset($methods[array_search($method,$methods,true)]);
                $i = 0;
                break;
            }
        }
        if ( $i != 0 ) {
            $end = microtime(true);
            echo "Time taken with $method = ".round(($end-$start)*1000.0,3)."ms\n";
        }
    }
}



$true_targets = [
    'Giant array' => range(0, 500),
    'Tiny array' => range(0, 20),
];


$g = range(0,10);
unset($g[0]);

$false_targets = [
    'Large array 1' => range(0, 100) + ['a'=>'a'] + range(101, 200),
    'Large array 2' => ['a'=>'a'] + range(0, 200),
    'Tiny array' => range(0, 10) + ['a'=>'a'] + range(11, 20),
    'Gotcha array' => $g,
];

$methods = [
    'method_1',
    'method_3',
    'method_4',
    'method_5',
    'method_6',
    'method_7',
    'method_8'
];


foreach($false_targets as $targetName => $target){
    echo "==== Benchmark using $targetName expecing FALSE ====\n";
    benchmark($methods, $target, false);
    echo "\n";
}
foreach($true_targets as $targetName => $target){
    echo "==== Benchmark using $targetName expecting TRUE ====\n";
    benchmark($methods, $target, true);
    echo "\n";
}
TylerY86
источник
2

Вот метод, который я использую:

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

Обратите внимание, что это не учитывает особых случаев, таких как:

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

unset($a[1]);

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

Извините, не могу помочь вам с этим. Он также несколько эффективен для массивов приличного размера, поскольку не создает ненужных копий. Именно эти мелочи делают Python и Ruby намного приятнее в написании ...: P

AL X
источник
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);
}

?>

Оба эти примера, набравшие наибольшее количество баллов, работают некорректно с такими массивами, как $array = array('foo' => 'bar', 1)

KillEveryBody
источник
+1 Ваш is_list () - IMO лучший ответ. Некоторые люди не имеют ни малейшего представления о сложности времени и пространства, а о нативной и PHP-скриптовой функции ...
e2-e4
2

Это тоже будет работать ( демо ):

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

Обратите внимание, что основной смысл этого ответа - информировать вас о существовании, SplFixedArrayа не поощрять вас использовать исключения для подобных тестов.

Гордон
источник
2

Я думаю, что определение скалярного массива будет варьироваться в зависимости от приложения. То есть некоторые приложения потребуют более строгого понимания того, что называется скалярным массивом, а некоторые приложения требуют более свободного понимания.

Ниже я представляю 3 метода различной строгости.

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

Еще один пост от источника . Подходит кодировка json_encodebson_encode). То же самое относится и к соблюдению 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
Почему issetи array_key_exists? Разве последнего не будет достаточно?
МакФедр
@mcfedr да, было бы - isset()проверка здесь полностью избыточна.
Марк Амери
@mcfedr, @ mark-amery по соображениям производительности. isset()быстрее чем array_key_exists(). см ilia.ws/archives/...
lazycommit
@lazycommit Это будет зависеть от вашего массива, в зависимости от того, будет ли он лучше с или без, не то, чтобы он имел массив с большим количеством nulls, но также не так вероятно, что у вас достаточно большой массив, чтобы была заметная разница в производительности. с помощью обеих проверок
McFedr
2
если вам нужно проверить , если она будет соответствовать json_encode, вы могли бы просто проверить первый символ строки, возвращенное json_encode($your_arr)- будь то [или {;-)
Pilat
2

Может ли это быть решением?

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

Предостережение заключается в том, что курсор массива сбрасывается, но я бы сказал, что, вероятно, функция используется до того, как массив будет даже пройден или использован.

Kat Lim Ruiz
источник
Эта функция возвращает false для обоих array("a", "b")и array("a", "b" => "B")только проверяет первый ключ. Кстати, is_longэто просто псевдонимis_int .
Пан
1
Откровенно говоря, я думаю, что это было бы очень эффективно в подавляющем большинстве случаев и гораздо более эффективно, чем альтернативы. Если вы понимаете последствия этого метода и понимаете, что он будет работать для вас, это, вероятно, лучший выбор.
Гершом
Это просто неправильно; это только смотрит на первый ключ.
Марк Амери
@MarkAmery задавался вопрос, как отличить чисто последовательные массивы от чисто ассоциативных массивов. Этот ответ делает именно это и является наиболее эффективным из них. Неопределенное поведение для смешанных массивов прекрасно в контексте вопроса. +1
Тобиа
@ Tobia Я не думаю, что большинство людей согласятся с вами классифицировать, скажем, [7 => 'foo', 2 => 'bar']как «смешанный» массив, который частично, но не «чисто» последовательный. Это кажется мне совершенно неправильным использованием слов.
Марк Амери
2

Многие решения здесь элегантны и симпатичны, но плохо масштабируются и требуют большого объема памяти или ресурсов процессора. Большинство из них создают 2 новые точки данных в памяти с этим решением с обеих сторон сравнения. Чем больше массив, тем сложнее и дольше используются процесс и память, и вы теряете преимущество оценки короткого замыкания. Я провел некоторое тестирование с несколькими разными идеями. Попытка избегать array_key_exists, поскольку это дорого, а также избегать создания новых больших наборов данных для сравнения. Я чувствую, что это простой способ определить, является ли массив последовательным.

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

Вы запускаете один счетчик в основном массиве и сохраняете одно целое число. Затем вы перебираете массив и проверяете точное совпадение, перебирая счетчик. Вы должны иметь от 1 до подсчета. В случае сбоя происходит короткое замыкание, что повышает производительность, когда оно ложно.

Первоначально я делал это с помощью цикла for и проверял isset ($ arr [$ i]), но это не обнаружит нулевые ключи, для которых требуются array_key_exists, и, как мы знаем, это худшая функция для скорости.

Постоянно обновляя переменные через foreach, чтобы проверять вместе с итератором, не превышающим его целочисленный размер, давайте использовать PHP, он встроен в оптимизацию памяти, кеширование и сборку мусора, чтобы поддерживать очень низкое использование ресурсов.

Кроме того, я утверждаю, что использование array_keys в foreach глупо, когда вы можете просто запустить $ key => $ value и проверить ключ. Зачем создавать новую точку данных? Когда вы абстрагируете ключи массива, вы сразу же потребляете больше памяти.

geilt
источник
1

ответы уже даны, но есть слишком много дезинформации о производительности. Я написал этот небольшой тестовый скрипт, который показывает, что метод foreach является самым быстрым.

Отказ от ответственности: следующие методы были скопированы из других ответов

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

Результаты:

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

Или вы можете просто использовать это:

Arr::isAssoc($array)

который проверит, содержит ли массив какой-либо нечисловой ключ или:

Arr:isAssoc($array, true)

проверить, является ли массив строго последовательным (содержит автоматически сгенерированные целые ключи от 0 до n-1 )

используя эту библиотеку.

Minwork
источник
0

Если в PHP нет встроенной функции для этого, вы не сможете сделать это менее чем за O (n) - перечисление всех ключей и проверка целочисленного типа. На самом деле, вы также хотите убедиться, что дыр нет, поэтому ваш алгоритм может выглядеть так:

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

Но зачем? Просто предположите, что массив того типа, который вы ожидаете. Если это не так, это просто взорвется вам в лицо - это динамическое программирование для вас! Протестируй свой код и все будет хорошо ...

Дарен Томас
источник
1
Обычно, если предположить, что массив является желаемым типом, это то, что нужно. Но в моем случае я перебираю многомерный массив и форматирую вывод в зависимости от типа массива данного узла.
Wilco
0

Я сравниваю разницу между ключами массива и ключами результата array_values ​​() массива, который всегда будет массивом с целочисленными индексами. Если ключи одинаковы, это не ассоциативный массив.

function isHash($array) {
    if (!is_array($array)) return false;
    $diff = array_diff_assoc($array, array_values($array));
    return (empty($diff)) ? false : true;
}
philroy
источник
-1; это использует O(n)дополнительную память, когда $arrayесть nпредметы, и письмо (someboolean) ? false : trueвместо того, чтобы !somebooleanужасно и безвозмездно многословно.
Марк Амери