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

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

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

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

и это:

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

Ответ 1

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

  • Во-первых, как определить, имеет ли массив только числовые ключи
  • Во-вторых, как определить, имеет ли массив последовательные числовые ключи, начиная с 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

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

Многие комментаторы в этом вопросе не понимают, как работают массивы в 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 в качестве ключа массива, он будет автоматически преобразован в "").

Ответ 5

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

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("[email protected]" => "b");    // Strings may contain non-alphanumeric characters.
array("stack\t\"over\"\r\nflow 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 a valid hexadecimal number.
array("013" => "b");                        // Not converted to integers even though it 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-разрядным целым числом со знаком.

Ответ 6

Speed-накрест:

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

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

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

Ответ 7

Фактически наиболее эффективным способом является:

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

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

Ответ 8

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

Ответ 9

Я использовал как 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.

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

Ответ 10

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

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, если все ключи были целыми. Если нет, цикл заканчивается преждевременно, потому что ключ имеет строку типа, а оператор после цикла for возвращает false. Последняя функция дополнительно добавляет от одной до $базы после каждого сравнения, чтобы проверить, имеет ли следующий ключ правильное значение. Строгое сравнение также позволяет проверить, имеет ли ключ целочисленный тип. Базовая часть $base = (int) $в первом разделе цикла for может быть опущена, когда $base опущена или если вы убедитесь, что она вызывается только с помощью целого числа. Но так как я не могу быть уверен в всех, я оставил его. Заявление выполняется только один раз. Я думаю, что это наиболее эффективные решения:

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

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

Ответ 11

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

  • с отверстиями в индексе (например, 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;
}

Ответ 12

Я заметил два популярных подхода к этому вопросу: один использовал 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() выполняется намного быстрее.

Ответ 13

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

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

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

Ответ 14

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

Ответ 15

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

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

Ответ 16

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

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

if (array_is_indexed($array)) {  }

Или:

if (array_is_assoc($array)) {  }

Ответ 17

Мое решение:

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.

Ответ 18

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

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

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

Ответ 19

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

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

Ответ 20

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

Ответ 21

Это тоже сработает (demo):

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

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

Ответ 22

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

Ниже представлены 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 <[email protected]>
 */

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

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

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

Ответ 23

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

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

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

Ответ 24

Еще один быстрый из источник. Установите кодировку 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__)
        );
    }
}

Ответ 25

После некоторого локального бенчмаркинга, отладки, проверки компилятора, профилирования и злоупотребления 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";
}

Ответ 26

ответы уже даны, но слишком много дезинформации о производительности. Я написал этот маленький тест script, который показывает, что метод 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

Ответ 27

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

for i in 0 to len(your_array):
    if not defined(your-array[i]):
        # this is not an array array, it an associative array :)

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

Ответ 28

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

Ответ 29

Еще один способ сделать это.

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

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

}

Ответ 30

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

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