Хорошо, это не вопрос "как получить все уникальные" или "Как удалить дубликаты из моего массива в php". Это вопрос о временной сложности.
Я понял, что array_unique
несколько O (n ^ 2 - n), и здесь моя реализация:
function array_unique2($array)
{
$to_return = array();
$current_index = 0;
for ( $i = 0 ; $i < count($array); $i++ )
{
$current_is_unique = true;
for ( $a = $i+1; $a < count($array); $a++ )
{
if ( $array[$i] == $array[$a] )
{
$current_is_unique = false;
break;
}
}
if ( $current_is_unique )
{
$to_return[$current_index] = $array[$i];
}
}
return $to_return;
}
Однако, сравнивая это с array_unique
i, получилось следующее:
Тестирование (array_unique2)... Операция заняла 0,52146291732788 с.
Тестирование (array_unique)... Операция заняла 0.28323101997375 с.
Что делает array_unique в два раза быстрее, мой вопрос в том, почему (оба имеют одинаковые случайные данные)?
И мой друг написал следующее:
function array_unique2($a)
{
$n = array();
foreach ($a as $k=>$v)
if (!in_array($v,$n))
$n[$k]=$v;
return $n;
}
который в два раза быстрее, чем встроенный в php.
Хотелось бы узнать, почему?
Какова временная сложность array_unique и in_array?
Edit
Я удалил счетчик ($ array) из обоих циклов и просто использовал переменную в верхней части функции, которая набрала 2 секунды на 100 000 элементов!
Ответ 1
Пока я не могу говорить о встроенной функции array_unique, могу сказать, что алгоритм ваших друзей быстрее, потому что:
- Он использует одиночный цикл foreach, а не ваш цикл double for().
- Циклы forach обычно работают быстрее, чем для циклов в PHP.
- Он использовал одно, если (!) сравнение, когда вы использовали две структуры if()
- Единственный дополнительный вызов функции, созданный вашим другом, был in_array, тогда как вы дважды вызывали count().
- Вы сделали три объявления переменных, которые ваш друг не имел ($ a, $current_is_unique, $current_index)
Хотя ни один из этих факторов не является огромным, я могу видеть, где кумулятивный эффект заставит ваш алгоритм занять больше времени, чем ваши друзья.
Ответ 2
Сложность времени in_array()
равна O (n). Чтобы увидеть это, мы рассмотрим исходный код PHP.
Функция in_array()
реализована в ext/standard/array.c
. Все, что он делает, это вызов php_search_array()
, который содержит следующий цикл:
while (zend_hash_get_current_data_ex(target_hash, (void **)&entry, &pos) == SUCCESS) {
// checking the value...
zend_hash_move_forward_ex(target_hash, &pos);
}
То, откуда возникает линейная характеристика.
Это общая характеристика алгоритма, поскольку zend_hash_move_forward_ex()
имеет постоянное поведение: глядя на Zend/zend_hash.c
, мы видим, что он в основном просто
*current = (*current)->pListNext;
Что касается временной сложности array_unique()
:
- сначала будет создана копия массива, которая представляет собой операцию с линейной характеристикой
- тогда будет создан массив C
struct bucketindex
, и указатели в нашу копию массива будут помещены в эти ведра - снова линейная характеристика
- тогда
bucketindex
-array будет отсортирован по сортировке uss quicksort - n log
n в среднем
- и, наконец, отсортированный массив будет пройден, и дубликаты записей будут удалены из нашей копии массива - это должно быть линейным снова, предполагая, что удаление из нашего массива является операцией с постоянным временем
Надеюсь, что это поможет;)
Ответ 3
Попробуйте этот алгоритм. Он использует тот факт, что поиск ключей быстрее, чем in_array():
function array_unique_mine($A) {
$keys = Array();
$values = Array();
foreach ($A as $k => $v) {
if (!array_key_exists($v, $values)) {
$keys[] = $k;
$values[$v] = $v;
}
}
return array_combine($keys, $values);
}
Ответ 4
в ответе имеет несколько замечаний в отношении того, почему ваш метод друзей превосходит ваш. Заинтригованный беседой, следующей за Кристофом , я решил провести некоторые тесты самостоятельно.
Кроме того, я пробовал это с различной длиной случайных строк, и хотя результаты были разными, порядок был одинаков. Для краткости я использовал 6 символов в этом примере.
Обратите внимание, что array_unique5 фактически имеет те же ключи, что и native, 2 и 3, но просто выдает в другом порядке.
Результаты...
Testing 10000 array items of data over 1000 iterations:
array_unique6: 1.7561039924622 array ( 9998 => 'b', 9992 => 'a', 9994 => 'f', 9997 => 'e', 9993 => 'c', 9999 => 'd', )
array_unique4: 1.8798060417175 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 4 => 'c', 5 => 'd', )
array_unique5: 7.5023629665375 array ( 10 => 'd', 0 => 'b', 3 => 'e', 2 => 'f', 9 => 'c', 1 => 'a', )
array_unique3: 11.356487989426 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique: 22.535032987595 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique2: 62.107122898102 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
array_unique7: 71.557286024094 array ( 0 => 'b', 1 => 'a', 2 => 'f', 3 => 'e', 9 => 'c', 10 => 'd', )
И Код...
set_time_limit(0);
define('HASH_TIMES', 1000);
header('Content-Type: text/plain');
$aInput = array();
for ($i = 0; $i < 10000; $i++) {
array_push($aInput, chr(rand(97, 102)));
}
function array_unique2($a) {
$n = array();
foreach ($a as $k=>$v)
if (!in_array($v,$n))
$n[$k]=$v;
return $n;
}
function array_unique3($aOriginal) {
$aUnique = array();
foreach ($aOriginal as $sKey => $sValue) {
if (!isset($aUnique[$sValue])) {
$aUnique[$sValue] = $sKey;
}
}
return array_flip($aUnique);
}
function array_unique4($aOriginal) {
return array_keys(array_flip($aOriginal));
}
function array_unique5($aOriginal) {
return array_flip(array_flip(array_reverse($aOriginal, true)));
}
function array_unique6($aOriginal) {
return array_flip(array_flip($aOriginal));
}
function array_unique7($A) {
$keys = Array();
$values = Array();
foreach ($A as $k => $v) {
if (!array_key_exists($v, $values)) {
$keys[] = $k;
$values[$v] = $v;
}
}
return array_combine($keys, $values);
}
function showResults($sMethod, $fTime, $aInput) {
echo $sMethod . ":\t" . $fTime . "\t" . implode("\t", array_map('trim', explode("\n", var_export(call_user_func($sMethod, $aInput), 1)))) . "\n";
}
echo 'Testing ' . (count($aInput)) . ' array items of data over ' . HASH_TIMES . " iterations:\n";
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique($aInput);
$aResults['array_unique'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique2($aInput);
$aResults['array_unique2'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique3($aInput);
$aResults['array_unique3'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique4($aInput);
$aResults['array_unique4'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique5($aInput);
$aResults['array_unique5'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique6($aInput);
$aResults['array_unique6'] = microtime(1) - $fTime;
$fTime = microtime(1);
for ($i = 0; $i < HASH_TIMES; $i++) array_unique7($aInput);
$aResults['array_unique7'] = microtime(1) - $fTime;
asort($aResults, SORT_NUMERIC);
foreach ($aResults as $sMethod => $fTime) {
showResults($sMethod, $fTime, $aInput);
}
Результаты с использованием данных Кристофа из комментариев:
$aInput = array(); for($i = 0; $i < 1000; ++$i) $aInput[$i] = $i; for($i = 500; $i < 700; ++$i) $aInput[10000 + $i] = $i;
Testing 1200 array items of data over 1000 iterations:
array_unique6: 0.83235597610474
array_unique4: 0.84050011634827
array_unique5: 1.1954448223114
array_unique3: 2.2937450408936
array_unique7: 8.4412341117859
array_unique: 15.225166797638
array_unique2: 48.685120105743
Ответ 5
Массивы PHP реализованы как хеш-таблицы, т.е. их характеристики производительности отличаются от того, что вы ожидаете от "реальных" массивов. Пара ключей-значений массива дополнительно сохраняется в связанном списке, чтобы обеспечить быструю итерацию.
Это объясняет, почему ваша реализация настолько медленна по сравнению с вашим другом: для каждого числового индекса ваш алгоритм должен выполнять поиск хеш-таблицы, тогда как foreach()
-loop будет просто перебирать связанный список.
В следующей реализации используется обратная хеш-таблица и, возможно, самая быстрая из них (двурушная любезность joe_mucchiello):
function array_unique2($array) {
return array_flip(array_flip($array));
}
Это будет работать, только если значения $array
являются допустимыми ключами, то есть целыми числами или строками.
Я также повторил ваш алгоритм с помощью foreach()
-loops. Теперь это будет быстрее, чем ваш друг для небольших наборов данных, но все же медленнее, чем решение через array_flip()
:
function array_unique3($array) {
$unique_array = array();
foreach($array as $current_key => $current_value) {
foreach($unique_array as $old_value) {
if($current_value === $old_value)
continue 2;
}
$unique_array[$current_key] = $current_value;
}
return $unique_array;
}
Для больших наборов данных встроенная версия array_unique()
будет превосходить все остальные, кроме двухстрочного. Кроме того, версия, использующая in_array()
вашим другом, будет быстрее, чем array_unique3()
.
Подводя итог: собственный код для победы!
Еще одна версия, которая должна сохранять ключи и их порядок:
function array_flop($array) {
$flopped_array = array();
foreach($array as $key => $value) {
if(!isset($flopped_array[$value]))
$flopped_array[$value] = $key;
}
return $flopped_array;
}
function array_unique4($array) {
return array_flip(array_flop($array));
}
Это фактически enobrev array_unique3()
- я не проверял его реализации так же тщательно, как должен был...
Ответ 6
PHP работает медленнее, чем исходный машинный код (который, скорее всего, выполняется array_unique
).
Ваша вторая примерная функция (написанная вашим другом) интересна. Я не вижу, как это будет быстрее, чем встроенная реализация, если только native не удаляет элементы, а не создает новый массив.
Ответ 7
Я признаю, что я не очень хорошо понимаю собственный код, но, похоже, он копирует весь массив, сортирует его, а затем циклически удаляет дубликаты. В этом случае ваш второй фрагмент кода на самом деле является более эффективным алгоритмом, так как добавление в конец массива дешевле, чем удаление с середины его.
Имейте в виду, что у разработчиков PHP, вероятно, была хорошая причина для этого, как они это делают. Кто-нибудь хочет спросить их?
Ответ 8
Собственная функция PHP array_unique
реализована в C. Таким образом, он быстрее PHP, который должен быть переведен первым. Более того, PHP использует другой алгоритм, чем вы. Как я вижу, PHP сначала использует Quick sort для сортировки элементов, а затем удаляет дубликаты за один проход.
Почему у его друзей быстрее скорость, чем у его друзей? Поскольку он использует больше встроенных функций, которые пытаются их воссоздать.