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

В настоящее время я создаю метод сортировки, состоящий из значений из запроса mysql.

Вот краткий обзор массива:

    Array
    (
        [0] => Array
            (
                ['id'] = 1;
                ['countries'] = 'EN,CH,SP';
            )
        [1] => Array
            (
                ['id'] = 2;
                ['countries'] = 'GE,SP,SV';
            )
    )

Мне удалось сделать обычный usort на основе числовых значений идентификатора, но я скорее хочу отсортировать массив по содержимому поля «страны» (если он содержит заданную строку, в данном случае код страны), а затем по полю id.

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

in_array('EN', explode(",",$a['countries']) );

Как бы вы это сделали?

Спасибо!


Я действительно ничего не получаю с этим, к сожалению.

Вот что у меня есть на данный момент, и это не дает мне ничего, кроме ошибок: uasort() [function.uasort]: Invalid comparison function

function compare($a, $b) {
    global $usercountry;

        if ( in_array($usercountry, $a['countries']) && in_array($usercountry, $a['countries']) ) {
            $return = 0;
        }

        else if (in_array($usercountry, $a['countries'])) {
            $return = 1;
        }

        else {
            $return = -1;
        }

        return $return;


        }

        $array= usort($array, "compare");

Есть ли кто-нибудь, кто мог бы дать мне намек на то, как продолжать с этим?


person Industrial    schedule 13.01.2010    source источник
comment
Из любопытства, почему бы вам не отсортировать их из БД в первую очередь, например. ORDER BY country, id   -  person Gordon    schedule 13.01.2010
comment
Как вы говорите, он уже отсортирован по идентификатору, но это не решает часть проблемы in_array...   -  person Industrial    schedule 13.01.2010
comment
возможный дубликат Reference: все основные способы сортировки массивы и данные в PHP   -  person vzwick    schedule 04.04.2014


Ответы (4)


Лично я бы использовал пользовательскую (анонимную) функцию в сочетании с usort().

РЕДАКТИРОВАТЬ: Re - ваш комментарий. Надеюсь, это поставит вас на правильный путь. Эта функция дает равный приоритет элементам, которые оба имеют EN или не имеют EN, или скорректированный приоритет, когда только один имеет EN.

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($b['countries'],'EN');
    if (($ac !== false && $bc !== false) || ($ac == false && $bc == false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    else {
        return -1;
    }
});

Эта функция, с другой стороны, дает равный приоритет, если у обоих есть EN, более высокий, если у одного есть EN, и выполняет сравнение текста, если ни у одного нет EN.

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($b['countries'],'EN');
    if ($ac !== false && $bc !== false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    elseif ($bc !== false) {
        return -1;
    }
    else {
        if ($a['countries'] == $b['countries']) {
            return 0;
        }
        elseif($a['countries'] > $b['countries']) {
            return 1;
        }
        else {
            return -1;
        }
    }
});

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

$ac = array_flip(explode(',',$a['countries']));
$bc = array_flip(explode(',',$b['countries']));
switch (true) {
    case array_key_exists('EN',$ac) && !array_key_exists('EN',$bc):
        return 1;
    case array_key_exists('DE',$ac) && !array_key_exists('EN',$bc) && !array_key_exists('EN',$bc):
        return 1;
    // and so on
}

Больше правок!

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

Пример массива

$array = array(
    array(
        'countries' => 'EN,DE,SP',
    ),
    array(
        'countries' => 'EN,CH,SP',
    ),
    array(
        'countries' => 'DE,SP,CH',
    ),
    array(
        'countries' => 'DE,SV,SP',
    ),
    array(
        'countries' => 'EN,SP,FR',
    ),
    array(
        'countries' => 'DE,FR,CH',
    ),
    array(
        'countries' => 'CH,EN,SP',
    ),

);

Процедура сортировки

$rankings = array(
    'EN' => 10,
    'SP' => 8,
    'FR' => 7,
    'DE' => 5,
    'CH' => 3,
    'SV' => 1,
);
usort($array, function (&$a, &$b) use ($rankings) {
    if (isset($a['_score'])) {
        $aScore = $a['_score'];
    }
    else {
        $aScore = 0;
        $aCountries = explode(',',$a['countries']);
        foreach ($aCountries as $country) {
            if (isset($rankings[$country])) {
                $aScore += $rankings[$country];
            }
        }
        $a['_score'] = $aScore;
    }

    if (isset($b['_score'])) {
        $bScore = $b['_score'];
    }
    else {
        $bScore = 0;
        $bCountries = explode(',',$b['countries']);
        foreach ($bCountries as $country) {
            if (isset($rankings[$country])) {
                $bScore += $rankings[$country];
            }
        }
        $b['_score'] = $bScore;
    }
    if ($aScore == $bScore) {
        return 0;
    }
    elseif ($aScore > $bScore) {
        return -1;
    }
    else {
        return 1;
    }
});

Примечание. Этот код отсортирует целые элементы с самым высоким рейтингом в начало массива. Если вы хотите обратное поведение, измените это:

    elseif ($aScore > $bScore) {

to

    elseif ($aScore < $bScore) {

Обратите внимание, что знак «больше» был заменен символом «меньше». Внесение этого изменения приведет к тому, что элементы с самым низким рейтингом будут отсортированы в начало массива. Надеюсь, все это поможет!

ВНИМАНИЕ ТАКЖЕ!

Этот код внесет небольшое изменение в ваш массив, добавив элемент _score к каждому массиву. Надеюсь, это не проблема, поскольку, сохранив это значение, я буквально смог увеличить скорость более чем вдвое (с 0,00038 до 0,00041 до 0,00016-00018 в моих тестах). Если нет, удалите блоки if, которые извлекают кэшированное значение, и позволяйте содержимому блоков else выполняться каждый раз, за ​​исключением, конечно, части, в которой хранится значение оценки.

Кстати, вот var_export() дамп массива после его сортировки:

array (
  0 => array (
    'countries' => 'EN,SP,FR',
    '_score' => 25,
  ),
  1 => array (
    'countries' => 'EN,DE,SP',
    '_score' => 23,
  ),
  2 => array (
    'countries' => 'EN,CH,SP',
    '_score' => 21,
  ),
  3 => array (
    'countries' => 'CH,EN,SP',
    '_score' => 21,
  ),
  4 => array (
    'countries' => 'DE,SP,CH',
    '_score' => 16,
  ),
  5 => array (
    'countries' => 'DE,FR,CH',
    '_score' => 15,
  ),
  6 => array (
    'countries' => 'DE,SV,SP',
    '_score' => 14,
  ),
)

Наслаждаться!

person Dereleased    schedule 13.01.2010

Наконец нашел эту замечательную функцию на PHP.net:

        function array_msort($array, $cols)
        {
            $colarr = array();
            foreach ($cols as $col => $order) {
                $colarr[$col] = array();
                foreach ($array as $k => $row) { $colarr[$col]['_'.$k] = strtolower($row[$col]); }
            }
            $eval = 'array_multisort(';
            foreach ($cols as $col => $order) {
                $eval .= '$colarr[\''.$col.'\'],'.$order.',';
            }
            $eval = substr($eval,0,-1).');';
            eval($eval);
            $ret = array();
            foreach ($colarr as $col => $arr) {
                foreach ($arr as $k => $v) {
                    $k = substr($k,1);
                    if (!isset($ret[$k])) $ret[$k] = $array[$k];
                    $ret[$k][$col] = $array[$k][$col];
                }
            }
            return $ret;

        }

Вот как выглядит каждая страна: $array['countries'] = in_array($needle, $haystack); }

$array = $array = array_msort($array, array('countries'=>SORT_DESC, 'id'=>SORT_ASC));

Спасибо всем за вашу помощь!

person Industrial    schedule 13.01.2010
comment
Ой... eval()? Совершенно ненужный. - person mickmackusa; 30.05.2019

Вы можете рассмотреть array_walk и array_walk_recursive и array_map, которые в сочетании друг с другом могут привести к тому, что вы хотите сделать.

person Kitson    schedule 13.01.2010
comment
Этот намек мог быть комментарием под вопросом. - person mickmackusa; 30.05.2019

В настоящее время я создаю метод сортировки, состоящий из значений из запроса mysql.

ПРАВДА:
Использование чего-либо, кроме MySQL, для сортировки вашего набора результатов будет менее эффективным (с php вызов usort() или array_multisort() будет более запутанным и сложным в обслуживании) и, следовательно, неуместным.

SQL: (Демо)

ORDER BY IF(LOCATE('EN', countries), 0, 1), id;

Это отдает приоритет значениям столбцов countries, которые содержат EN, а затем сортируются по id ASC.


Для всех, кто не работает с результирующим набором sql или по какой-то причине не может манипулировать запросом, я рекомендую usort(). PHP7 предлагает прекрасный новый оператор, который выполняет сравнение и возвращает одно из трех значений (-1, 0, 1). Этот оператор ласково называется "оператор космического корабля" и выглядит вот так <=>.

PHP: (Демо)

$test = [
    ['id' => 1, 'countries' => 'EN,CH,SP'],
    ['id' => 2, 'countries' => 'GE,SP,SV'],
    ['id' => 3, 'countries' => 'PR,SP,IT'],
    ['id' => 4, 'countries' => 'EN'],
    ['id' => 5, 'countries' => 'SP,EN'],
    ['id' => 6, 'countries' => 'SV,SP,EN'],
    ['id' => 7, 'countries' => 'GE,SP'],
    ['id' => 8, 'countries' => 'FR'],
    ['id' => 9, 'countries' => 'RU,EN'],
    ['id' => 10, 'countries' => 'EN,SP,IT'],
    ['id' => 11, 'countries' => 'SP,GR'],
    ['id' => 12, 'countries' => 'GR,EN']
];

usort($test, function($a, $b) {
    return [strpos($a['countries'], 'EN') === false, $a['id']] <=> [strpos($b['countries'], 'EN') === false, $b['id']];
});

var_export($test);

Выход:

array (
  0 => 
  array (
    'id' => 1,
    'countries' => 'EN,CH,SP',
  ),
  1 => 
  array (
    'id' => 4,
    'countries' => 'EN',
  ),
  2 => 
  array (
    'id' => 5,
    'countries' => 'SP,EN',
  ),
  3 => 
  array (
    'id' => 6,
    'countries' => 'SV,SP,EN',
  ),
  4 => 
  array (
    'id' => 9,
    'countries' => 'RU,EN',
  ),
  5 => 
  array (
    'id' => 10,
    'countries' => 'EN,SP,IT',
  ),
  6 => 
  array (
    'id' => 12,
    'countries' => 'GR,EN',
  ),
  7 => 
  array (
    'id' => 2,
    'countries' => 'GE,SP,SV',
  ),
  8 => 
  array (
    'id' => 3,
    'countries' => 'PR,SP,IT',
  ),
  9 => 
  array (
    'id' => 7,
    'countries' => 'GE,SP',
  ),
  10 => 
  array (
    'id' => 8,
    'countries' => 'FR',
  ),
  11 => 
  array (
    'id' => 11,
    'countries' => 'SP,GR',
  ),
)

Элементы массива по обе стороны от оператора космического корабля оцениваются слева направо (слева [0] против справа [0], а затем переходят к паре значений [1], если между двумя [0] есть «связь». ценности).

Если === false смотрит назад, позвольте мне объяснить...

Если в строке стран будет найдено EN, условие будет оценено как false. При сравнении true и false помните, что true равно 1, а false равно 0. Нам нужна сортировка ASC, поэтому мы хотим поставить ложные результаты перед истинными результатами, поэтому строки, содержащие EN, должны возвращать false. Надеюсь, это проясняет логику.

person mickmackusa    schedule 29.05.2019