Определить наиболее распространенное расширение в массиве

#php #regex #arrays #sorting #loops

#php #регулярное выражение #массивы #сортировка #циклы

Вопрос:

Примите во внимание следующий код:

 $files = array('1.js', '1.css', '2.js', '2.css', '3.js', '3.png');
$extensions = array();

foreach ($files as $file)
{
    $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));

    if (empty($extensions[$extension]) === true)
    {
        $extensions[$extension] = 0;
    }

      $extensions[$extension];
}

arsort($extensions); // array('js' => 3, 'css' => 2, 'png' => 1)

$common_extension = key($extensions); // js
  

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

 $files = array('1.js', '1.css', '2.js', '2.css', '3.js', '3.png');
$extensions = array_count_values(array_map('strtolower', preg_replace('~^.*[.](.*)$~', '$1', $files)));

arsort($extensions, SORT_NUMERIC);

$common_extension = key($extensions);
  

Но это перебирает массив 3 раза, и это preg_replace() не является пуленепробиваемым… Есть идеи?

Комментарии:

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

2. Вы можете написать свой собственный метод, который содержит strtolower и ваш pathinfo фрагмент для использования в array_map вызове. Это сэкономит вам одну итерацию. И если вы не хотите array_count_values запускать массив во второй раз, вам снова придется рассчитывать на себя.

Ответ №1:

Я бы сделал что-то вроде этого:

 <?php
function getCommon($array, $result = array()) {
    foreach ($array as $k => $v) { $array[$k] = strtolower(pathinfo($v, PATHINFO_EXTENSION)); }
    $ext = array_count_values($array); arsort($ext,SORT_NUMERIC);
    $k = array_keys($ext); $k0 = $k[0];
    if ($ext[$k0] > $ext[$k[1]]) { $result[] = $k0; }
    else { foreach ($ext as $k => $v) { if ($v == $ext[$k0]) { $result[] = $k; } } }
    return $result;
}

$files = array('1.js', '2.js', '3.png', '4.css');
print_R($files);
print_R(getCommon($files));

$files2 = array('1.js', '2.js', '3.png', '4.png', '5.css');
print_R($files2);
print_R(getCommon($files2));
?>
  

Это вернет массив, значения которого будут общими расширениями, даже если их много.

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

Комментарии:

1. Кажется, это делает именно то, что делает мой первый фрагмент, но вы перебираете массив 4 раза, я не получаю улучшения.

2. Он также возвращает draws, но не использует regex и он короче.

Ответ №2:

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

 function count_ext($array){
    $ret = array();
    foreach($array as $ext){
        $ext = strtolower(pathinfo($ext, PATHINFO_EXTENSION));
        if( !isset($ret[$ext]) ) $ret[$ext] = 0;
        $ret[$ext]  ;
    }
    arsort($ret);
    return $ret;
}
  

Я не знаю, сколько элементов в ваших массивах и насколько критичен ко времени ваш вариант использования, но этот метод подойдет хорошо.