Classificação Multidim Array: priorize se a coluna contém substring, então encomende por uma segunda coluna

StackOverflow https://stackoverflow.com/questions/2059255

Pergunta

Atualmente, estou criando um método de classificação que consiste em valores de uma consulta MySQL.

Aqui está uma breve visão da matriz:

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

Consegui fazer um uso normal com base nos valores de ID numéricos, mas prefiro classificar a matriz pelo conteúdo do campo "países" (se ele contiver uma sequência de set, um código de país neste caso) e, em seguida, pelo campo de identificação.

O snippet a seguir foi a minha primeira idéia de como fazê -lo, mas não tenho idéia de como incorporá -lo em uma função de trabalho:

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

Como você faria?

Obrigado!


Infelizmente, não estou chegando a lugar nenhum com isso.

Aqui está o que tenho no momento, e não está me dando nada além de erros: 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");

Existe alguém que possa me dar uma dica de como continuar com isso?

Foi útil?

Solução

Pessoalmente, eu usaria uma função personalizada (anônima) em conjunto com usort().

EDIT: RE - seu comentário. Espero que isso o coloque no caminho certo. Essa função fornece prioridade igual aos elementos que ambos têm EN ou não têm EN, ou prioridade ajustada quando apenas um tem 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;
    }
});

Essa função, por outro lado, dá a mesma prioridade se ambos tiverem EN, mais alto se houver EN, e fizer uma comparação de texto se nenhum deles tiver 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;
        }
    }
});

Mais uma vez, espero que isso lhe dê uma direção suficiente para avançar por conta própria. Se você estiver tendo algum problema, sinta -se à vontade para postar mais comentários e tentarei ajudar. Uma nota se você estiver ligando para comparar várias propriedades com peso: experimente um bloco de switch descolado, por exemplo

$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
}

Mais edições!

Na verdade, eu estava pensando mais no problema da classificação complexa e criei a solução a seguir, para sua consideração. Isso permitirá que você defina rankings numéricos com base em palavras -chave que apareceriam no índice dos países. Aqui está o código, incluindo um exemplo:

Exemplo de matriz

$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',
    ),

);

Rotina de classificação

$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;
    }
});

Nota: Este código classificará o mais alto ranking inteira para o topo da matriz. Se você quer um comportamento reverso, mude isso:

    elseif ($aScore > $bScore) {

para

    elseif ($aScore < $bScore) {

Observe que o maior do que foi alterado para um símbolo menos do que Fazer essa alteração resultará no As entradas de classificação mais baixas sendo classificadas no topo da matriz. Espero que tudo isso ajude!

Nota também!

Esse código fará uma pequena alteração em sua matriz, pois adiciona o elemento _score a cada matriz. Espero que isso não seja um problema, pois, ao armazenar esse valor, eu consegui literalmente aumentar a velocidade em mais que o dobro (0,00038-.00041 até 0,00016-.00018 nos meus benchmarks). Caso contrário, remova o if blocos que recuperam o valor em cache e deixam o conteúdo do else Os blocos são executados sempre, exceto, é claro, para a peça que armazena o valor da pontuação.

A propósito, aqui está um var_export() despejo da matriz depois que foi classificada:

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,
  ),
)

Apreciar!

Outras dicas

Finalmente encontrei esta função maravilhosa em 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;

        }

É assim que cada país se parece: $ Array ['países'] = in_array ($ agulha, $ haystack); }

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

Obrigado a todos por sua ajuda!

Você pode considerar array_walk e array_walk_recursive e array_map, que, quando combinado, talvez para fazer o que você deseja fazer.

Tente com Array_mulisort.

Verificação de saída uasort Para ver como usar uma função de comparação definida pelo usuário.

Atualmente, estou criando um método de classificação que consiste em valores de uma consulta MySQL.

VERDADE:
Usar qualquer coisa que não usort() ou array_multisort() A chamada será mais complicada e mais difícil de manter) e, portanto, inapropriada.

SQL: (Demonstração)

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

Isso prioriza countries valores de coluna que contêm EN Então classifica id ASC.


Para quem não está lidando com um conjunto de resultados de SQL ou não pode manipular a consulta por algum motivo, eu endosso usort(). O PHP7 oferece um belo novo operador que executa uma comparação e retorna um dos três valores (-1, 0, 1). Este operador é carinhosamente chamado de "operador de nave espacial" e se parece com isso <=>.

PHP: (Demonstração)

$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);

Resultado:

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',
  ),
)

Os elementos da matriz em ambos os lados do operador da nave espacial são avaliados da esquerda para a direita (esquerda [0] vs rodoviário [0], depois passando para o par de [1] valores se houver um "empate" entre os dois [0] valores).

Se o === false olha para trás, deixe -me explicar ...

Se EN é encontrado nas cordas dos países, a condição avaliará como false. Ao comparar true e false, lembre-se disso true equivale a 1 e false equivale a 0. queremos classificação ASC, por isso queremos colocar resultados falsos antes de resultados verdadeiros, strings de ergo contendo EN precisar para retornar falso. Espero que isso esclareça a lógica.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top