it-swarm.com.ru

Как отсортировать многомерный массив в PHP

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

function f_parse_csv($file, $longest, $delimiter)
{
  $mdarray = array();
  $file    = fopen($file, "r");
  while ($line = fgetcsv($file, $longest, $delimiter))
  {
    array_Push($mdarray, $line);
  }
  fclose($file);
  return $mdarray;
}

Мне нужно иметь возможность указать столбец для сортировки, чтобы он переставил строки. Один из столбцов содержит информацию о дате в формате Y-m-d H:i:s, и я хотел бы иметь возможность сортировки с самой последней датой, являющейся первой строкой.

197
Melikoth

Вы можете использовать array_multisort ()

Попробуйте что-то вроде этого:

foreach ($mdarray as $key => $row) {
    // replace 0 with the field's index/key
    $dates[$key]  = $row[0];
}

array_multisort($dates, SORT_DESC, $mdarray);

Для PHP> = 5.5.0 просто извлеките столбец для сортировки. Нет необходимости в цикле:

array_multisort(array_column($mdarray, 0), SORT_DESC, $mdarray);
206
Shinhan

Представляем: очень обобщенное решение для PHP 5.3+

Я хотел бы добавить свое собственное решение здесь, так как оно предлагает функции, которых нет в других ответах.

В частности, преимущества этого решения включают в себя:

  1. Он может использоваться повторно : вы указываете столбец сортировки как переменную, а не жестко его кодируете.
  2. Это гибкий : вы можете указать несколько столбцов сортировки (столько, сколько хотите) - дополнительные столбцы используются в качестве разрыва связей между элементами, которые изначально сравниваются одинаково.
  3. Это обратимо : вы можете указать, что сортировка должна быть обратной - индивидуально для каждого столбца.
  4. Это расширяемое : если набор данных содержит столбцы, которые нельзя сравнивать "тупо" (например, строки даты), вы также можете указать, как преобразовать эти элементы к значению, которое можно сравнивать напрямую (например, экземпляр DateTime).
  5. Это ассоциативно, если вы хотите : этот код заботится о сортировке элементов, но вы выбираете фактическую сортировку функция (usort или uasort).
  6. Наконец, он не использует array_multisort: хотя array_multisort удобен, он зависит от создания проекции всех ваших входных данных перед сортировкой. Это требует времени и памяти и может быть просто непомерно большим, если ваш набор данных большой.

Код

function make_comparer() {
    // Normalize criteria up front so that the comparer finds everything tidy
    $criteria = func_get_args();
    foreach ($criteria as $index => $criterion) {
        $criteria[$index] = is_array($criterion)
            ? array_pad($criterion, 3, null)
            : array($criterion, SORT_ASC, null);
    }

    return function($first, $second) use (&$criteria) {
        foreach ($criteria as $criterion) {
            // How will we compare this round?
            list($column, $sortOrder, $projection) = $criterion;
            $sortOrder = $sortOrder === SORT_DESC ? -1 : 1;

            // If a projection was defined project the values now
            if ($projection) {
                $lhs = call_user_func($projection, $first[$column]);
                $rhs = call_user_func($projection, $second[$column]);
            }
            else {
                $lhs = $first[$column];
                $rhs = $second[$column];
            }

            // Do the actual comparison; do not return if equal
            if ($lhs < $rhs) {
                return -1 * $sortOrder;
            }
            else if ($lhs > $rhs) {
                return 1 * $sortOrder;
            }
        }

        return 0; // tiebreakers exhausted, so $first == $second
    };
}

Как пользоваться

В этом разделе я буду предоставлять ссылки, которые сортируют этот пример набора данных:

$data = array(
    array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
    array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
    array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
    array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);

Основы

Функция make_comparer принимает переменное число аргументов, определяющих желаемую сортировку, и возвращает функцию, которую вы должны использовать в качестве аргумента для usort или uasort.

Простейший вариант использования - передать ключ, который вы хотели бы использовать для сравнения элементов данных. Например, чтобы отсортировать $data по элементу name, вы должны выполнить

usort($data, make_comparer('name'));

увидеть его в действии.

Ключ также может быть числом, если элементы являются числовыми индексированными массивами. Для примера в вопросе это будет

usort($data, make_comparer(0)); // 0 = first numerically indexed column

увидеть его в действии.

Несколько столбцов сортировки

Вы можете указать несколько столбцов сортировки, передав дополнительные параметры make_comparer. Например, чтобы отсортировать по "номеру", а затем по столбцу с нулевым индексом:

usort($data, make_comparer('number', 0));

увидеть его в действии.

Расширенные возможности

Дополнительные возможности доступны, если вы указываете столбец сортировки в виде массива вместо простой строки. Этот массив должен быть пронумерован и должен содержать следующие элементы:

0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)

Давайте посмотрим, как мы можем использовать эти функции.

Обратная сортировка

Сортировать по названию по убыванию:

usort($data, make_comparer(['name', SORT_DESC]));

увидеть его в действии.

Сортировать по убыванию числа, а затем по убыванию имени:

usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));

увидеть его в действии.

Пользовательские прогнозы

В некоторых сценариях может потребоваться сортировка по столбцу, значения которого плохо подходят для сортировки. Столбец "день рождения" в примере набора данных соответствует этому описанию: нет смысла сравнивать дни рождения как строки (потому что, например, "01.01.1980" предшествует "10.10.1970"). В этом случае мы хотим указать, как проецировать фактические данные в форму, которую можно сравнивать непосредственно с желаемой семантикой.

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

Следует отметить, что хотя проекции похожи на пользовательские функции сравнения, используемые с usort и family, они проще (вам нужно только преобразовать одно значение в другое) и используют все функциональные возможности, уже встроенные в make_comparer.

Давайте рассмотрим примерный набор данных без проекции и посмотрим, что произойдет:

usort($data, make_comparer('birthday'));

увидеть его в действии.

Это был не желаемый результат. Но мы можем использовать date_create как проекцию:

usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));

увидеть его в действии.

Это правильный порядок, который мы хотели.

Есть много других вещей, которых могут достичь прогнозы. Например, быстрый способ получить сортировку без учета регистра - использовать strtolower в качестве проекции.

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

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

usort($data, make_comparer(
    ['number', SORT_DESC],
    ['birthday', SORT_ASC, 'date_create']
));

увидеть его в действии.

338
Jon

С sort . Вот общее решение, которое вы можете использовать для разных столбцов:

class TableSorter {
  protected $column;
  function __construct($column) {
    $this->column = $column;
  }
  function sort($table) {
    usort($table, array($this, 'compare'));
    return $table;
  }
  function compare($a, $b) {
    if ($a[$this->column] == $b[$this->column]) {
      return 0;
    }
    return ($a[$this->column] < $b[$this->column]) ? -1 : 1;
  }
}

Сортировать по первому столбцу:

$sorter = new TableSorter(0); // sort by first column
$mdarray = $sorter->sort($mdarray);
31
troelskn

Многострочная сортировка с использованием замыкания

Вот еще один подход, использующий uasort () и анонимную функцию обратного вызова (замыкание). Я использовал эту функцию регулярно. требуется PHP 5. - больше никаких зависимостей!

/**
 * Sorting array of associative arrays - multiple row sorting using a closure.
 * See also: http://the-art-of-web.com/php/sortarray/
 *
 * @param array $data input-array
 * @param string|array $fields array-keys
 * @license Public Domain
 * @return array
 */
function sortArray( $data, $field ) {
    $field = (array) $field;
    uasort( $data, function($a, $b) use($field) {
        $retval = 0;
        foreach( $field as $fieldname ) {
            if( $retval == 0 ) $retval = strnatcmp( $a[$fieldname], $b[$fieldname] );
        }
        return $retval;
    } );
    return $data;
}

/* example */
$data = array(
    array( "firstname" => "Mary", "lastname" => "Johnson", "age" => 25 ),
    array( "firstname" => "Amanda", "lastname" => "Miller", "age" => 18 ),
    array( "firstname" => "James", "lastname" => "Brown", "age" => 31 ),
    array( "firstname" => "Patricia", "lastname" => "Williams", "age" => 7 ),
    array( "firstname" => "Michael", "lastname" => "Davis", "age" => 43 ),
    array( "firstname" => "Sarah", "lastname" => "Miller", "age" => 24 ),
    array( "firstname" => "Patrick", "lastname" => "Miller", "age" => 27 )
);

$data = sortArray( $data, 'age' );
$data = sortArray( $data, array( 'lastname', 'firstname' ) );
10
feeela
function cmp($a, $b)
{
$p1 = $a['price'];
$p2 = $b['price'];
return (float)$p1 > (float)$p2;
}
uasort($my_array, "cmp");

http://qaify.com/sort-an-array-of-associative-arrays-by-value-of-given-key-in-php/

6
Kamal

Я знаю, что прошло 2 года с тех пор, как на этот вопрос был задан и дан ответ, но вот еще одна функция, которая сортирует двумерный массив. Он принимает переменное число аргументов, позволяя вам передавать более одного ключа (то есть имя столбца) для сортировки. PHP 5.3 обязательно.

function sort_multi_array ($array, $key)
{
  $keys = array();
  for ($i=1;$i<func_num_args();$i++) {
    $keys[$i-1] = func_get_arg($i);
  }

  // create a custom search function to pass to usort
  $func = function ($a, $b) use ($keys) {
    for ($i=0;$i<count($keys);$i++) {
      if ($a[$keys[$i]] != $b[$keys[$i]]) {
        return ($a[$keys[$i]] < $b[$keys[$i]]) ? -1 : 1;
      }
    }
    return 0;
  };

  usort($array, $func);

  return $array;
}

Попробуйте это здесь: http://www.exorithm.com/algorithm/view/sort_multi_array

6
Mike C

Функция "Usort" - это ваш ответ.
http://php.net/usort

3
Jan Hančič

Вот класс php4/php5, который будет сортировать одно или несколько полей:

// a sorter class
//  php4 and php5 compatible
class Sorter {

  var $sort_fields;
  var $backwards = false;
  var $numeric = false;

  function sort() {
    $args = func_get_args();
    $array = $args[0];
    if (!$array) return array();
    $this->sort_fields = array_slice($args, 1);
    if (!$this->sort_fields) return $array();

    if ($this->numeric) {
      usort($array, array($this, 'numericCompare'));
    } else {
      usort($array, array($this, 'stringCompare'));
    }
    return $array;
  }

  function numericCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      if ($a[$sort_field] == $b[$sort_field]) {
        continue;
      }
      return ($a[$sort_field] < $b[$sort_field]) ? ($this->backwards ? 1 : -1) : ($this->backwards ? -1 : 1);
    }
    return 0;
  }

  function stringCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      $cmp_result = strcasecmp($a[$sort_field], $b[$sort_field]);
      if ($cmp_result == 0) continue;

      return ($this->backwards ? -$cmp_result : $cmp_result);
    }
    return 0;
  }
}

/////////////////////
// usage examples

// some starting data
$start_data = array(
  array('first_name' => 'John', 'last_name' => 'Smith', 'age' => 10),
  array('first_name' => 'Joe', 'last_name' => 'Smith', 'age' => 11),
  array('first_name' => 'Jake', 'last_name' => 'Xample', 'age' => 9),
);

// sort by last_name, then first_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort by first_name, then last_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'first_name', 'last_name'));

// sort by last_name, then first_name (backwards)
$sorter = new Sorter();
$sorter->backwards = true;
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort numerically by age
$sorter = new Sorter();
$sorter->numeric = true;
print_r($sorter->sort($start_data, 'age'));
2
Devon

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

function sort2d_bycolumn($array, $column, $method, $has_header)
  {
  if ($has_header)  $header = array_shift($array);
  foreach ($array as $key => $row) {
    $narray[$key]  = $row[$column]; 
    }
  array_multisort($narray, $method, $array);
  if ($has_header) array_unshift($array, $header);
  return $array;
  }
  • $ array - это массив MD, который вы хотите отсортировать.
  • $ column - это столбец, по которому вы хотите отсортировать.
  • $ method - это способ выполнения сортировки, например SORT_DESC.
  • $ has_header имеет значение true, если первая строка содержит значения заголовков, которые вы не хотите сортировать.
0
Melikoth

Я попробовал несколько популярных ответов array_multisort () и usort (), и ни один из них не помог мне. Данные просто перемешиваются, а код не читается. Вот быстрое грязное решение. ПРЕДУПРЕЖДЕНИЕ: используйте это, только если вы уверены, что разбойник не вернется, чтобы преследовать вас позже!

Допустим, каждая строка в вашем мультимассиве выглядит следующим образом: name, stuff1, stuff2:

// Sort by name, pull the other stuff along for the ride
foreach ($names_stuff as $name_stuff) {
    // To sort by stuff1, that would be first in the contatenation
    $sorted_names[] = $name_stuff[0] .','. name_stuff[1] .','. $name_stuff[2];
}
sort($sorted_names, SORT_STRING);

Нужны ваши вещи обратно в алфавитном порядке?

foreach ($sorted_names as $sorted_name) {
    $name_stuff = explode(',',$sorted_name);
    // use your $name_stuff[0] 
    // use your $name_stuff[1] 
    // ... 
}

Да, это грязно. Но супер легко, не заставит твою голову взорваться.

0
PJ Brunet