За последние 24 часа нас посетили 9545 программистов и 634 робота. Сейчас ищет 201 программист ...

[задачка] Как реализовать сортировку по комбинации условий (как в SQL)

Тема в разделе "Прочие вопросы по PHP", создана пользователем artoodetoo, 11 фев 2020.

Метки:
  1. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    Сравните как работает SQL
    Код (SQL):
    1. INSERT INTO person (`name`, `age`) VALUES
    2.   ('Alfred', '40'),
    3.   ('Mark', '40'),
    4.   ('Lue', '45'),
    5.   ('Ameli', '38'),
    6.   ('Barb', '38');
    7.  
    8. SELECT *
    9. FROM person
    10. ORDER BY age DESC, name ASC
    и как работает неуклюжая попытка воспроизвести это на голом PHP:
    Код (PHP):
    1. usort($array,function($a, $b){
    2.     return -strnatcasecmp($a['age'], $b['age']);// desc
    3. });
    4.  
    5. usort($array,function($a, $b){
    6.     return strnatcasecmp($a['name'], $b['name']);// asc
    7. });
    Желаемый результат вот такой:
    Lue, Alfred, Mark, Ameli, Barb
    Но т.к. следующая сортировка затирает результат предшествующей, то получается:
    Alfred, Ameli, Barb, Lue, Mark

    Хотелось бы написать универсальную функцию, которая будет соединять переданные в неё замыкания и производить накопительную сортировку.

    Важное дополнение: array_multisort() клёвый, но хочется комбинировать функции. Это бы дало бОльшую гибкость.
     
    #1 artoodetoo, 11 фев 2020
    Последнее редактирование: 11 фев 2020
  2. acho

    acho Активный пользователь

    С нами с:
    28 дек 2016
    Сообщения:
    800
    Симпатии:
    187
    Адрес:
    Санкт-Петербург
    artoodetoo нравится это.
  3. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    @acho это решение для конкретного примера, да. Но было бы ещё круче получить решение в общем виде для двух и более функций-компараторов.
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    up.
    PHP:
    1. /** @var array */
    2. protected $data = [];
    3.  
    4. /** @var array */
    5. protected $comparators = [];
    6.  
    7. public function __construct($data = null)
    8. {
    9.     $this->data = $data ?? [];
    10.     return $this;
    11. }
    12.  
    13. public function orderBy($field, $order = 'asc')
    14. {
    15.     $multiplier = strcasecmp($order, 'asc') ? 1 : -1;
    16.     $this->comparators[] = function ($a, $b) use ($field, $multiplier) {
    17.         return strcasecmp($b[$field], $a[$field]) * $multiplier;
    18.     };
    19.     return $this;
    20. }
    21.  
    22. // . . .
    23.  
    24. public function get()
    25. {
    26.     usort(
    27.         $this->data,
    28.         function ($a, $b) {
    29.             foreach ($this->comparators as $c) {
    30.                 $res = $c($a, $b);
    31.                 if ($res != 0)
    32.                     return $res;
    33.             }
    34.             return 0;
    35.         }
    36.     );
    37.     return $this->data;
    38. }
    пример использования (задача из реальной практики)
    PHP:
    1.     $recordset = getFromExternalSource();
    2.     $recordset = data($recordset)
    3.         ->whereIn('name', [$fname, $lname])
    4.         ->whereAnyIn('addresses', [$state, $county])
    5.         ->orderByBetterMatch('addresses', [$state, $county], 'desc') // накапливаем...
    6.         ->orderByExactMatch('name', "{$fname} {$lname}", 'desc') // накапливаем...
    7.         ->limit(100)
    8.         ->get(); // применяем все накопленные сортировки и отдаём
     
  5. miketomlin

    miketomlin Активный пользователь

    С нами с:
    9 авг 2016
    Сообщения:
    2.299
    Симпатии:
    366
    Какие ф-ции? array_multisort поддерживает не только направления, но и типы сортировки:
    PHP:
    1.     $b, SORT_DESC, SORT_NUMERIC,
    2.     $a, SORT_ASC, SORT_STRING
    3. );
    Для большей наглядности вместо 38 можно взять к примеру 380, чтобы увидеть натуральную сортировку.
     
  6. miketomlin

    miketomlin Активный пользователь

    С нами с:
    9 авг 2016
    Сообщения:
    2.299
    Симпатии:
    366
    P.S. Для использования произвольных ф-ций в доках рекомендуют сортировать по измененной копии:
    PHP:
    1.     $b, SORT_DESC, SORT_NUMERIC,
    2.     array_map('strtolower', $a), SORT_ASC, SORT_STRING,
    3.     $a
    4. );
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    а поддерживает он такое: сортировать по порядку, но нужное значение удерживать первым?
    или сортировать по полю-подмассиву, по максимальной похожести элементов на образец?
    и т.п.

    короче, @miketomlin, спасибо за желание помочь, но нет. тебя наверное тоже бесит когда ты сказал "пиво не пью", а тебе в ответ "ну вот есть хорошее пиво", да?
     
  8. miketomlin

    miketomlin Активный пользователь

    С нами с:
    9 авг 2016
    Сообщения:
    2.299
    Симпатии:
    366
    Я не думал, что мой пост может кого-то взбесить. На решение задачки, естественно, не претендую. Просто решил дополнить твое «важное дополнение». Ну и вопрос про ф-ции задал не ради фигуры речи.
    --- Добавлено ---
    P.S. Меня можно Миша/Михаил звать.
    --- Добавлено ---
    P.P.S. Если «просто потрещать» в создаваемых тобой темах не приветствуется, ты пиши ;)
     
  9. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    Очень приятно, я Саша :)
    Да я не против поболтать, наверное получилось слишком резко, сорри. Так-то я всегда рад общению.
    --- Добавлено ---
    В очередной раз заметил, что сам факт обсуждения уже помогает снять шоры с глаз. Понял, что limit надо тоже отложенным делать. Потому что если резать неотсортированные данные, можно потерять нужное.

    правильная последовательность: фильтры → сортировка → обрезание
     
  10. romach

    romach Старожил

    С нами с:
    26 окт 2013
    Сообщения:
    2.871
    Симпатии:
    701
    PHP:
    1. <?php
    2.  
    3. use Tuck\Sort\Sort;
    4.  
    5. require __DIR__.'/vendor/autoload.php';
    6.  
    7. $array = [
    8.     ['name' => 'Alfred', 'age' => 40],
    9.     ['name' => 'Mark', 'age' => 40],
    10.     ['name' => 'Lue', 'age' => 45],
    11.     ['name' => 'Ameli', 'age' => 38],
    12.     ['name' => 'Barb', 'age' => 38],
    13. ];
    14.  
    15. $result = Sort::chain()
    16.     ->desc(function ($item) {
    17.         return $item['age'];
    18.     })
    19.     ->asc(function ($item) {
    20.         return $item['name'];
    21.     })->values($array);
    22.  
    23. foreach ($result as $item) {
    24.     echo $item['name'].' '.$item['age'].PHP_EOL;
    25. }
    Код (Text):
    1. Lue 45
    2. Alfred 40
    3. Mark 40
    4. Ameli 38
    5. Barb 38
    https://github.com/rosstuck/sort

    вроде оно ) ну как, если хочешь универсальности, то наверное придется форкнуть и навернуть абстракций, но в целом довольно близко, если я правильно понял что тебе нужно.
     
    artoodetoo нравится это.
  11. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.072
    Симпатии:
    958
    Адрес:
    там-сям
    Оно :)
    --- Добавлено ---
    Синтаксис не такой к какому я привел, но суть сортировки та же. Спасибо!