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

В настоящее время я создаю метод сортировки, который состоит из значений из запроса 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");

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

Ответы [ 6 ]

12 голосов
/ 13 января 2010

Лично я бы использовал пользовательскую (анонимную) функцию вместе с 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) {

до

    elseif ($aScore < $bScore) {

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

ПРИМЕЧАНИЕ ТАКЖЕ!

Этот код внесет небольшое изменение в ваш массив, добавив элемент _score в каждый массив. Надеюсь, это не проблема, так как, сохраняя это значение, я буквально смог увеличить скорость более чем вдвое (.00038-.00041 до .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,
  ),
)

Наслаждайтесь!

2 голосов
/ 14 января 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 ['country'] = in_array ($ needle, $ haystack); }

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

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

1 голос
/ 13 января 2010

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

0 голосов
/ 30 мая 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, поэтому мы хотим поставить ложные результаты перед истинными, то есть строки ergo, содержащие EN нужно чтобы вернуть false. Надеюсь, это проясняет логику.

0 голосов
/ 13 января 2010

Проверьте uasort , чтобы узнать, как использовать пользовательскую функцию сравнения.

0 голосов
/ 13 января 2010

Попробуйте с array_mulisort .

...