Магия сортировки array_multisort() + ошибка

KotOFF

Новичок
При создании условия для сортировки - функция array_multisort(), получаю ошибку:
Array sizes are inconsistent in
PHP:
function mySort($array){
    $title = $year = $symbols_rus = $symbols_eng = array();
    $rus = '/^[а-яА-Я]$/';
    $eng = '/^[a-zA-Z]$/';
    foreach($array as $key=>$row){
        //Узнаем 1-й символ значения 'title'
        $symbol = substr($row['title'],0, 1);
        if(preg_match($rus,$symbol)){
                $symbols_rus[$key] = $symbol;
         }
        else{
                $symbols_eng[$key] = $symbol;
        }
        $title[$key] = $row['title'];
        $year[$key] = $row['year'];
        //Количество символов в значении 'title'
        $count_str[$key] = strlen($row['title']);
    }
   //Сортируем по алфавиту(кирилица), дальше по году(по возрастанию), дальше по количеству букв в названии фильма (по улыбанию)! ДАЛЬШЕ БУДЕТ - СОРТИРОВКА ПО ЛАТИНСКИХ НАЗВАНИЯХ В ЗНАЧЕНИИ "title" 
    array_multisort($symbols_rus, $year, SORT_ASC, $count_str, SORT_DESC,  $array);
    return $array;
}
//МАССИВ
$array = array(
    array('title'=>'Автор книги по ФИЛЬМАМ','year'=>'2010'),
    array('title'=>'Овен','year'=>'2014'),
    array('title'=>'Один дома','year'=>'2001'),
    array('title'=>'Багратион','year'=>'2005'),
    array('title'=>'Аяериканский пирог','year'=>'2005'),
    array('title'=>'Астрал','year'=>'2005'),
    array('title'=>'Doom','year'=>'2004'),
    array('title'=>'Один дома 2','year'=>'2014')
);
Где я сделал ошибку, помогите ?
 

ksnk

прохожий
Мест, в которых ты сделал ошибку чуть более чем дофига.
- array_multisort хочет, чтобы количество элементов в каждом сортируемом массиве было одинаково. Очевидно, что в массиве
symbols_rus их будет меньше. Вероятно, в случае если регулярка таки не смогла найти первую русскую букву - массив стоит заполнить каким-то другим символом, например пробелом. Или более "тяжелым" символом, так как пробел легче при сортировке. То же и для первой английской.

Остальные ошибки могу зависеть от версии php/ Скорее всего в 8 версии все будет работать и так, однако

- substr($row['title'],0, 1) для версии php 74 и ниже вернет первый байт utf комбинации русского символа. Лучше использовать mb_substr, или выковыривать первый символ регуляркой, раз уж и так регулярки в работе.
- В условиях тотального распространения utf-8 в регулярку нужно бы запихать флаг /u просто потому, что в некоторых версиях php он необходим, а в некоторых не помешает.
- каждый массив в параметрах array_multisort , за исключением последнего, мог бы сопровождаться флагом направления сортировки. Тут не так, для первого массива флага нет. Это не критичная ошибка, но так как в остальных случаях направление указано - неплохо бы указать и здесь.
 

KotOFF

Новичок
Мест, в которых ты сделал ошибку чуть более чем дофига.
- array_multisort хочет, чтобы количество элементов в каждом сортируемом массиве было одинаково. Очевидно, что в массиве
symbols_rus их будет меньше. Вероятно, в случае если регулярка таки не смогла найти первую русскую букву - массив стоит заполнить каким-то другим символом, например пробелом. Или более "тяжелым" символом, так как пробел легче при сортировке. То же и для первой английской.

Остальные ошибки могу зависеть от версии php/ Скорее всего в 8 версии все будет работать и так, однако

- substr($row['title'],0, 1) для версии php 74 и ниже вернет первый байт utf комбинации русского символа. Лучше использовать mb_substr, или выковыривать первый символ регуляркой, раз уж и так регулярки в работе.
- В условиях тотального распространения utf-8 в регулярку нужно бы запихать флаг /u просто потому, что в некоторых версиях php он необходим, а в некоторых не помешает.
- каждый массив в параметрах array_multisort , за исключением последнего, мог бы сопровождаться флагом направления сортировки. Тут не так, для первого массива флага нет. Это не критичная ошибка, но так как в остальных случаях направление указано - неплохо бы указать и здесь.
PHP:
if(preg_match($rus,$symbol)){
         $symbols_rus[$key] = $symbol;
         $symbols_eng[$key] = ' ';
}
else{
          $symbols_rus[$key] = ' ';
          $symbols_eng[$key] = $symbol;
}
Тогда я не могу отсортировать (значения"title" на русском, а затем - значения"title" на английском)
PHP:
array_multisort($symbols_rus, $year, SORT_ASC, $count_str, SORT_DESC, $symbols_eng, $array);
 

ksnk

прохожий
Для этого (сначала русский по алфавиту, потом английский по алфавиту) подойдет что-то такое
PHP:
if(preg_match($rus,$symbol)){
     $symbols_rus[$key] = $symbol;
     $symbols_eng[$key] = ' ';
}
else{
    $symbols_rus[$key] = 'ёё';
    $symbols_eng[$key] = $symbol;
}
Если порядок английский, потом русский - можно лепить тот же пробел
 

KotOFF

Новичок
Для этого (сначала русский по алфавиту, потом английский по алфавиту) подойдет что-то такое
PHP:
if(preg_match($rus,$symbol)){
     $symbols_rus[$key] = $symbol;
     $symbols_eng[$key] = ' ';
}
else{
    $symbols_rus[$key] = 'ёё';
    $symbols_eng[$key] = $symbol;
}
Если порядок английский, потом русский - можно лепить тот же пробел
Редактировал код:
PHP:
array_multisort($symbols_rus, $year, SORT_ASC, $count_str, SORT_DESC, $symbols_eng, $array);
изменив сортировку
PHP:
array_multisort($symbols_eng, SORT_ASC, $symbols_rus, SORT_ASC, $year, SORT_ASC, $count_str, SORT_DESC, $symbols_eng, $array);
Сортирует теперь русские значение "title" (алфавит. год. количество символов в значении 'title') -ВСЕ ОК,
а дальше сортирует английское значение "title" - НЕ КОРРЕКТНО!
Нужно: 1) значение "year" (по возрастанию), дальше, если год совпадает 2) по количество символов в значении 'title'
 

ksnk

прохожий
Вот, немного поправленый твой тестовый код. Первый символ, в силу старости моего тестового php и моей застарелой зависимости от регулярок, я выковыриваю регуляркой.
Тестовый массив добит "фильмами" с одним и тем же годом и разным количеством букв в названии. В каком месте там идет "неправильная" сортировка ?
PHP:
function mySort($array){
    $title = $year = $symbols_rus = $symbols_eng = array();
    $rus = '/^([а-яА-ЯёЁ])|./u';
    foreach($array as $key=>$row){
        //Узнаем 1-й символ значения 'title'
        if(!preg_match($rus,$row['title'],$m)) continue; // пустые названия пропускаем.
        if(empty($m[1])){
             $symbols_eng[$key] = $m[0];
             $symbols_rus[$key] = 'ёё';
         } else {
              $symbols_rus[$key] = $m[1];
            $symbols_eng[$key] = ' ';
         }
        $title[$key] = $row['title'];
        $year[$key] = $row['year'];
        //Количество символов в значении 'title'
        $count_str[$key] = strlen($row['title']);
    }
   //Сортируем по алфавиту(кирилица), дальше по году(по возрастанию), дальше по количеству букв в названии фильма (по улыбанию)! ДАЛЬШЕ БУДЕТ - СОРТИРОВКА ПО ЛАТИНСКИХ НАЗВАНИЯХ В ЗНАЧЕНИИ "title"
    array_multisort(
    $symbols_rus,SORT_ASC,
    $symbols_eng, SORT_ASC,
    $year, SORT_ASC,
    $count_str, SORT_DESC,
    $array);
    return $array;
}
//МАССИВ
$array = array(
    array('title'=>'Автор книги по ФИЛЬМАМ','year'=>'2010'),
    array('title'=>'Doom 25','year'=>'2004'),
    array('title'=>'Doom 2','year'=>'2004'),
    array('title'=>'Овен','year'=>'2014'),
    array('title'=>'Один дома','year'=>'2001'),
    array('title'=>'Doom 1','year'=>'2004'),
    array('title'=>'Багратион','year'=>'2005'),
    array('title'=>'Аяериканский пирог','year'=>'2005'),
    array('title'=>'Астрал','year'=>'2005'),
    array('title'=>'Doom','year'=>'2004'),
    array('title'=>'Speed','year'=>'2004'),
    array('title'=>'Astral','year'=>'2004'),
    array('title'=>'Один дома 2','year'=>'2014')
);
print_r(mySort($array));
В каком месте тебе не нравится результат ?
Запустить можно, например на https://www.w3schools.com/php/phptryit.asp?filename=tryphp_compiler
 

KotOFF

Новичок
Да, такой вариант хорош, но есть маленький нюанс (это временный массив), если в массиве значение "title" - будет первый символ цифра, нужно будет отсортировать в таком порядке:
1)Сортировка: кириллица (по алфавиту) -> cортировка по году (по возрастанию) -> сортировка по количеству символов значение "title" (найбольшое количество символов к меньшему)! -ЗДЕСЬ ВСЕ ОТЛИЧНО!
2)Сортировка: цифры по году (по возрастанию) -> сортировка по количеству символов значение "title" (найбольшое количество символов к меньшему)!
3)Сортировка: латиница по году (по возрастанию) -> сортировка по количеству символов значение "title" (найбольшое количество символов к меньшему)
ЕЩЁ, БУДУТ ЗНАЧЕНИЕ "title" - цифры! - ЭТОТ ЖЕ ВАРИАНТ, СОРТИРУЕТ ТАК - КАК СОРТИТУЕТ ПО КИРИЛЛИЦЕ, А НУЖНО УБРАТЬ ПРИОРИТЕТ АЛФАВИТ, А ДАЛЬШЕ КАК В ПУНКТЕ 3) !
В итоге, после сортировки должно получиться:
PHP:
$array = [
    ['title'=>'Аяериканский пирог','year'=>'2005'],
    ['title'=>'Астрал','year'=>'2005'],
    ['title'=>'Автор книги по ФИЛЬМАМ','year'=>'2010'],
    ['title'=>'Багратион','year'=>'2005'],
    ['title'=>'Один дома','year'=>'2001']
    ['title'=>'Один дома 2','year'=>'2014'],
    ['title'=>'Овен','year'=>'2014'],
    ['title'=>'5-й название - первый символ - цифра','year'=>'2002'],
    ['title'=>'100000-й название - первый символ - цифра','year'=>'2004'],
    ['title'=>'2-й название - первый символ - цифра','year'=>'2004'],
    ['title'=>'Doom','year'=>'2004'],
    ['title'=>'Avatar 2','year'=>'2008'],
    ['title'=>'Arstral','year'=>'2008'],
];
 

ksnk

прохожий
И кто мешает завести еще один массив -
symbols_num и складировать туда все нумера ?
Можно модифицировать регулярку для дополнительного ума, получится что-то вроде
PHP:
$rus = '/^([а-яА-ЯёЁ])|(\d)|./u';
...
        if (!empty($m[2])) { // это цифра ?
            $symbols_eng[$key] = ' ';
            $symbols_rus[$key] = 'ёё';
            $symbols_num[$key] = ' '; // убираем логику
        } else if (empty($m[1])) { // это латынь ?
            $symbols_eng[$key] = $m[0];
            $symbols_rus[$key] = 'ёё';
            $symbols_num[$key] = 'ёё';
        } else {
            $symbols_rus[$key] = $m[0];
            $symbols_eng[$key] = 'ёё';
            $symbols_num[$key] = ' ';
        }
...
array_multisort(
        $symbols_rus, SORT_ASC,
        $symbols_num, SORT_ASC,
        $symbols_eng, SORT_ASC,
        $year, SORT_ASC,
        $count_str, SORT_DESC,
        $array);
 

KotOFF

Новичок
И кто мешает завести еще один массив -
symbols_num и складировать туда все нумера ?
Можно модифицировать регулярку для дополнительного ума, получится что-то вроде
PHP:
$rus = '/^([а-яА-ЯёЁ])|(\d)|./u';
...
        if (!empty($m[2])) { // это цифра ?
            $symbols_eng[$key] = ' ';
            $symbols_rus[$key] = 'ёё';
            $symbols_num[$key] = ' '; // убираем логику
        } else if (empty($m[1])) { // это латынь ?
            $symbols_eng[$key] = $m[0];
            $symbols_rus[$key] = 'ёё';
            $symbols_num[$key] = 'ёё';
        } else {
            $symbols_rus[$key] = $m[0];
            $symbols_eng[$key] = 'ёё';
            $symbols_num[$key] = ' ';
        }
...
array_multisort(
        $symbols_rus, SORT_ASC,
        $symbols_num, SORT_ASC,
        $symbols_eng, SORT_ASC,
        $year, SORT_ASC,
        $count_str, SORT_DESC,
        $array);
Да, я пробовал таким способом. Все равно - некорректная сортировка!
2 нюанса:
1-е) ПО ЦИФРАМ:
Если первый символ ( ноль ) - то и сортировка будет все зависимости от количества общих символов (в значении),
Например:
PHP:
    [8] => Array
        (
            [title] => 2-й название - первый символ - цифра
            [year] => 2004
        )

    [9] => Array
        (
            [title] => 000000-й название - первый символ - цифра
            [year] => 2004
        )
Это - сортировка на данный момент, хотя, во 2-м массиве количество символов(в значении "title") больше
2-е) ПО ЛАТИНИЦЕ:
Сортирует по алфавиту и количеству символов в значении"title" (хотя, в латинице сортировка по алфавиту не нужна)!
Нужно по году(по возрастанию), если год совпадает, сортируем по количеству символов значение "title" (найбольшое количество символов к меньшему)!
Сейчас же сортирует (вот пример):
PHP:
    [10] => Array
        (
            [title] => Avatar 2
            [year] => 2008
        )
    [11] => Array
        (
            [title] => Arstral
            [year] => 2008
        )
    [12] => Array
        (
            [title] => Doom
            [year] => 2004
        )
 

ksnk

прохожий
С цифрой 0 косяк - условие empty срабатывает на строку с 0 в том числе.
Можно исправить , заменив на isset
PHP:
if (isset($m[2])) {
 

AnrDaemon

Продвинутый новичок
$rus = '/^[а-яА-Я]$/'; $eng = '/^[a-zA-Z]$/';

Про то, что в array_multisort сортируемый массив передаётся первым параметром, я вообще молчу. Вопрос - нахрена вообще с array_multisort ебаториться, когда у тебя всё равно функция? Не проще usort() сделать и не париться?
 

KotOFF

Новичок
С цифрой 0 косяк - условие empty срабатывает на строку с 0 в том числе.
Можно исправить , заменив на isset
PHP:
if (isset($m[2])) {
Да, верно!
Но вопрос - по поводу латинских названий - не исчез!
В латинице - сортировка по алфавиту не нужна (тоже же самое, что и цифры)!
Нужно по году(по возрастанию), если год совпадает, сортируем по количеству символов значение "title" (найбольшое количество символов к меньшему)!
Сортировка на данный момент:
PHP:
    [10] => Array
        (
            [title] => Avatar 2
            [year] => 2008
        )
    [11] => Array
        (
            [title] => Arstral
            [year] => 2008
        )
    [12] => Array
        (
            [title] => Doom
            [year] => 2004
        )
Хотя, в приоритете - должен быть год(по возрастанию), а сейчас - алфавит!
Латинский массив (после сортировки) должен быть:
PHP:
    [10] => Array
        (
            [title] => Doom
            [year] => 2004
        )
    [11] => Array
        (
            [title] => Avatar 2
            [year] => 2008
        )
    [12] => Array
        (
            [title] => Arstral
            [year] => 2008
        )
 

ksnk

прохожий
Про то, что в array_multisort сортируемый массив передаётся первым параметром, я вообще молчу
Нет. ВСЕ массивы попадающие в параметры функции будут отсортированы, не важно в каком месте они стоят. Порядок сортировки определяется слева-направо в списке параметров-массивов. Хотя, конечно, функцию довелось использовать только сейчас :)
 

ksnk

прохожий
Если сортировка по латинским буквам не нужна - то смотри как убрана логики сортировки по цифрам - тут то же самое - не присваивай букву массиву symbols_eng
PHP:
...
} else if (empty($m[1])) { // это латынь ?
    $symbols_eng[$key] = 'ёё';
В следующий раз неплохо бы сразу составить список хотелок и не вспоминать их по ходу решения задачи.
 

KotOFF

Новичок
Если сортировка по латинским буквам не нужна - то смотри как убрана логики сортировки по цифрам - тут то же самое - не присваивай букву массиву symbols_eng
PHP:
...
} else if (empty($m[1])) { // это латынь ?
    $symbols_eng[$key] = 'ёё';
В следующий раз неплохо бы сразу составить список хотелок и не вспоминать их по ходу решения задачи.
Огромное спасибо!
Я думал - делать так изначально (ВАРИАНТ):
Массив - разделить на три массива (по значению "title"):
1-й массив - КИРИЛЛИЦА
2-й массив - ЦИФРЫ
3-й массив - ЛАТЫНЬ
Дальше отсортировать каждый массив (array_multisort)
И в конце объединить все три массива (по нужному порядку - array_merge)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Я бы на code review такой write-only code не пропустил, заставил бы перепиcать.
Тут и неоптимальный алгоритм, и ненужная сложность, и хак с неявными особенностями неуместной языковой конструкции, и одержимость элементарными типами, и "большой комок грязи".
В сильных командах с таким кодом карьерный путь будет очень прямой.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Чтобы не быть ДеБражелоном, расскажу как надо делать.
1. Удобно работать с данными, которые оформлены структурой. В PHP для этого классы.
Что-то вроде
class \fqdn\Movie {readonly string $title; readonly int $year; function charset(){} enum; function __toString(){}; function toArray(){}};
2. Есть фильмы с названиями на кириллице, названия которых начинаются с латинских букв. Алгоритм проверки по 1й букве ошибочен.
3. гонять регулярку только чтобы проверить 1й символ строки - неоптимально,
4. сортировку стоит вынести в коллекцию
5. multisort не дает выигрыша в скорости, тут нужен или генератор, или for ++i по массиву структур - наглядно, быстро, без подводных камней
 

ksnk

прохожий
Вот про usort от AnrDaemon - согласен. Заменить мутную генерацию массивов и вызов не менее мутной функции array_multisort на более менее линейную и понятную логику функции сортировки - это было бы правильно и, в перспективе, человеколюбиво. Вопрос только был про "магию мультисорта", но кого это в клубе волнует?
Но генерировать вместо этого целый класс, вероятно еще и не один?..
Просто чтобы напомнить. Вначале - была функция на 20 строк. По стилю и зависимостям от окружения - она нужна в единственном месте в единственном проекте.
 
Сверху