Як перевірити, чи масив PHP асоціативний чи послідовний?


781

PHP розглядає всі масиви як асоціативні, тому функцій немає вбудованих. Хтось може порекомендувати досить ефективний спосіб перевірити, чи містить масив лише числові клавіші?

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

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

і це:

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

382
У вашому коді помилка: Помідор - це фрукт.
Olle Härstedt

9
Цей метод має застереження, але часто я це просто роблю if (isset($array[0])), що просте і швидке. Звичайно, слід спочатку переконатися, що масив не порожній, і ви повинні мати певні знання щодо можливого вмісту масиву, щоб метод не міг вийти з ладу (наприклад, змішаний числовий / асоціативний або не послідовний).
Гра "Двомісний"

@ OlleHärstedt Не за версією Верховного суду США. ;-)
MC Імператор

Відповіді:


622

Ви задали два не зовсім еквівалентні питання:

  • По-перше, як визначити, чи має масив лише числові клавіші
  • По-друге, як визначити, чи має масив послідовні числові ключі, починаючи з 0

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

На перше запитання (просто перевірити, чи всі клавіші є цифровими) добре відповів капітан 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що якщо нульовий елемент є нульовим значенням, масив поверне помилкове значення. Нульове значення, як правило, має бути законним значенням у такому масиві.
OCDev

@MAChitgarha, ваша редакція змінила поведінку функції, не пояснюючи чому, і змусила її суперечити опису в прозі вище того, що вона насправді повинна робити. Я це повернув.
Марк Амері

431

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

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

Якщо є щонайменше один рядовий ключ, $arrayбуде розглядатися як асоціативний масив.


22
Цей метод набагато кращий, ніж здається. Якщо count (filtered_array) == count (original_array), то це масив Assoc. Якщо count (filtered_array) == 0, то це індексований масив. Якщо count (filtered_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% прогулянку масиву. Було б ефективніше, особливо якщо ви маєте справу з великими масивами, якби ви перевіряли наявність рядка чи 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;

52
Це буде дублювати значення в масиві, що потенційно є дуже дорогим. Вам набагато краще вивчити ключі масиву.
meagar

8
Я просто використав ==; Я не думаю, що тут немає потреби в ===. Але відповісти на "скасувати, і він не працює": як тільки ви видалите перший елемент, це вже не масив, індексований цілим числом, починаючи з 0. Отже, IMO він працює.
grantwparks

4
Погодьтеся з @grantwparks: Рідкий масив не індексується. Цікаво, що немає можливості насправді видалити елемент із середини індексованого масиву PHP, в основному оголошуючи всі масиви асоціативними та числовими - це лише версія "складання ключа для мене".
RickMeasham

7
Єдина проблема, яка у мене є, полягає в тому, що ми ===будемо витрачати час на перевірку рівності значень, хоча нас цікавлять лише ключі. З цієї причини я віддаю перевагу $k = array_keys( $arr ); return $k === array_keys( $k );версії.
Джессі

5
Додана примітка, це не вдається для масивів, визначених цифровими клавішами, які вийшли з ладу. наприклад, $ myArr = масив (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 розглядає всі масиви як асоціативні

не зовсім розумно (IMHO) писати функцію, яка перевіряє, чи масив асоціативний . Тож спочатку перше: що є ключем у масиві PHP ?:

Ключ може бути або цілим числом або рядком .

Це означає, що є 3 можливі випадки:

  • Випадок 1. усі ключі є числовими / цілими числами .
  • Справа 2. усі клавіші - це рядки .
  • Випадок 3. деякі клавіші - це рядки , деякі - числові / цілі числа .

Ми можемо перевірити кожен випадок за допомогою наступних функцій.

Випадок 1: всі ключі є числовими / цілими числами .

Примітка . Ця функція повертає істину і для порожніх масивів.

//! 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: усі клавіші - це рядки .

Примітка . Ця функція повертає істину і для порожніх масивів.

//! 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. деякі клавіші - це рядки , деякі - числові / цілі числа .

Примітка . Ця функція повертає істину і для порожніх масивів.

//! 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а. ключі є числовими / цілими числами , послідовними та нульовими .

Примітка . Ця функція повертає істину і для порожніх масивів.

//! 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
Крім того, як я вже згадую нижче, це передбачає створення дублікату масиву до того, що перевіряється, що робить його дуже дорогим для великих масивів та потенційним джерелом аварій, що випадають з пам'яті на спільних хостах.
підперсон

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

2
Це єдина відповідь (на момент мого коментаря), яка може мати справу з наступним: $ array = array (0 => 'blah', 2 => 'yep', 3 => 'wahey')
Shabbyrobe

але array('1'=>'asdf', '2'=>'too')буде розглядатися як асоціативний масив, хоча його насправді немає (ключі насправді є рядком)
Капітан 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
@Pang, ти прав. Я подумав, що ваш коментар спочатку, безумовно, має бути помилковим, але, на мій подив, [0] == ['a']у PHP (оскільки 0 == 'a', і, справді, 0 == 'banana'). ==Оператор PHP божевільний.
Марк Амерді

2
Це недостатньо ефективно, оскільки це стосується виклику array_keys порівняно з просто перевірки, поки ви не знайдете не послідовний цілий індекс. Під капотом ви все одно робите це , але ви вже копіювали великий масив.
підперсон

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блоків.

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


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 і робить заяву після повернення циклу true, якщо всі ключі були цілими. Якщо ні, цикл закінчується передчасно, оскільки ключ є рядком типу, а вислів після циклу for for повернеться хибним. Остання функція додатково додає один базу до $ після кожного порівняння, щоб мати можливість перевірити, чи є наступний ключ правильним значенням. Суворе порівняння змушує перевірити, чи ключ має ціле число. Частина $ base = (int) $ у першому розділі циклу for for може бути залишена, коли $ base опущено або якщо ви переконаєтесь, що вона викликається лише за допомогою цілого числа. Але оскільки я не можу бути впевненим у всіх, я залишив це. Заява виконується лише один раз, у будь-якому випадку. Я думаю, що це найбільш ефективні рішення:

  • Пам'ять мудра: немає копіювання даних або діапазонів ключів. Виконання array_values ​​або array_keys може здатися коротшим (менший код), але майте на увазі, що відбувається у фоновому режимі, коли ви здійснюєте цей дзвінок. Так, є більше (видимих) тверджень, ніж у деяких інших рішеннях, але це не те, що враховується, чи не так?
  • Враховуючи час: окрім того, що копіювання / вилучення даних та / або ключів також вимагає часу, це рішення є більш ефективним, ніж робити передбачення. Знову-таки, передбачення може здатися більш ефективним для деяких, оскільки воно коротше, але на задньому плані передбачення також викликає скидання, натискання клавіш та поруч із цим циклом. Але крім того, він також вимагає, щоб перевірити стан кінця, чого тут уникнути через поєднання з цілою ціною.

Пам'ятайте, що ключ масиву може бути лише цілим чи рядком, а строго числовий рядок, такий як "1" (але не "01") буде переведений у ціле число. Це те, що робить перевірку цілого ключа єдиною необхідною операцією, крім підрахунку, якщо ви хочете, щоб масив був послідовним. Природно, якщо is_indexed_array повертає false, масив може розглядатися як асоціативний. Я кажу "бачив", бо насправді вони всі є.


1
Це найкраща відповідь. Визначення "асоціативного" або "числового" масиву залежить від конкретної ситуації.
Пато

Якщо foreach менш ефективний, ніж метод, який використовується тут, окрім незручності використання двох різних функцій, продуктивність цього рішення краще, ніж моя (попередня). Я підозрюю, що це не так, оскільки foreach рекомендується як найшвидший спосіб пройти масив.
підперсон

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 ​​(масив), він буде перетворений на послідовний.
позолочений

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

Один із способів наблизитись до цього - це piggyback on json_encode, у якого вже є свій внутрішній метод розмежування асоціативного масиву та індексованого масиву для виведення правильного JSON.

Це можна зробити, перевіривши, чи є перший символ, що повернувся після кодування, {(асоціативний масив) або [(індексований масив).

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

На мою думку, ksort () не потрібен. Це рішення працює, але воно повинно перевірити, чи $ arr недійсне, а якщо json_encode не вдалося, тож спробуйте / зловити. + це не дуже оптимально, якщо $ arr велика.
lucbonnin

7

Відповідей уже багато, але ось метод, на який спирається Ларавель у своєму класі 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


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

Цей алгоритм шалений, простий, зрозумілий.
Бені

Це не спрацює, якщо у вас є послідовний масив асоціативних рядків.
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)) {  }

3

Я знаю, що додати відповідь до цієї величезної черги трохи безглуздо, але ось читабельне O (n) рішення, яке не потребує дублювання будь-яких значень:

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

Замість того, щоб перевіряти клавіші, щоб побачити, чи всі вони чисельні, ви перебираєте ключі, які були б там для числового масиву, і переконайтесь, що вони існують.


ще один момент. масив у формі [1,2,null,4]не вдасться, але це правильний масив. тож я додав деякі вдосконалення в stackoverflow.com/a/25206156/501831 з додатковою array_key_existsперевіркою)
lazycommit

-1; isset()тут неправильний інструмент, оскільки він поверне помилковим, якщо значення встановлено, але є 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.


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

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


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)


+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). Так само є відповідність Java-масиву.

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

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

Застереження очевидно, що курсор масиву скидається, але я б сказав, що, ймовірно, функція використовується до того, як масив навіть буде пройдений або використаний.


Ця функція повертає брехня для обох 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 і перевірити ключ. Навіщо створювати нову точку даних? Після того, як ви абстрагуєтесь від клавіш масиву, ви негайно витратили більше пам'яті.


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)

перевірити, чи масив є строго послідовним (містить автоматично створені клавіші int від 0 до n-1 )

використовуючи цю бібліотеку.


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
Зазвичай, саме припущення масиву є бажаним типом. Але в моєму випадку я перебираю багатовимірний масив і форматую висновок залежно від типу масиву даного вузла.
Вілько

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

-1; це використовує O(n)додаткову пам'ять, коли $arrayє nелементи, а писати (someboolean) ? false : trueзамість цього !somebooleanє жахливим і безоплатно багатослівним.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.