Classificar uma matriz por chaves com base em outra matriz?

153

É possível no PHP fazer algo assim? Como você escreveria uma função? Aqui está um exemplo. A ordem é a coisa mais importante.

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

E eu gostaria de fazer algo como

$properOrderedArray = sortArrayByArray($customer, array('name', 'dob', 'address'));

Porque no final eu uso um foreach () e eles não estão na ordem correta (porque anexo os valores a uma string que precisa estar na ordem correta e não conheço antecipadamente todas as chaves da matriz / valores).

Eu olhei através das funções de matriz interna do PHP, mas parece que você só pode classificar alfabeticamente ou numericamente.

alex
fonte

Respostas:

347

Basta usar array_mergeou array_replace. Array_mergefunciona iniciando com a matriz que você fornece (na ordem correta) e substituindo / adicionando as chaves com dados da sua matriz real:

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$properOrderedArray = array_merge(array_flip(array('name', 'dob', 'address')), $customer);
//Or:
$properOrderedArray = array_replace(array_flip(array('name', 'dob', 'address')), $customer);

//$properOrderedArray -> array('name' => 'Tim', 'address' => '123 fake st', 'dob' => '12/08/1986', 'dontSortMe' => 'this value doesnt need to be sorted')

ps - Estou respondendo a essa pergunta 'obsoleta', porque acho que todos os loops dados como respostas anteriores são um exagero.

Darkwaltz4
fonte
31
Funciona bem se você tiver chaves de seqüência de caracteres, mas não para a numérica. PHP Docs: "Se as matrizes de entrada tiverem as mesmas chaves de seqüência de caracteres, o valor posterior dessa chave substituirá a anterior. Se, no entanto, as matrizes contiverem teclas numéricas, o valor posterior não substituirá o valor original, mas será anexado ".
Bolbol
7
Bom, mas e se as chaves não existirem nos valores? Eu preciso disso, mas só se qualquer uma das teclas existe ... Provavelmente precisa de um foreach sobre ele, então ...
Solomon Closson
5
para o meu caso, é array_replace em vez de array_merge. array_merge combina ambos os valores em vez de substituir a segunda matriz nas chaves ordenadas.
Neofreko 12/11/14
3
Eu me deparei com sua solução há alguns anos atrás, enquanto procurava por algo diferente - e pensei comigo mesmo: isso é extremamente eficiente em comparação com os loops. Agora, preciso da sua solução e demorei uma hora pesquisando para encontrá-la novamente! Obrigado!
Michael Michael
4
Além disso, se a matriz 'order' (ou seja, array ('name', 'dob', 'address')) tiver mais chaves que a matriz a ser classificada, array_intersect adicional da matriz classificada resultante com a matriz original será cortada chaves dispersas que foram adicionadas em array_merge.
bbe
105

Ai está:

function sortArrayByArray(array $array, array $orderArray) {
    $ordered = array();
    foreach ($orderArray as $key) {
        if (array_key_exists($key, $array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }
    return $ordered + $array;
}
Eran Galperin
fonte
12
Então você pode juntar 2 matrizes com um sinal de +? Eu nunca soube disso, estou usando array_merge()!
Alex
3
Isso é melhor do que usar usort()ou uasort()?
Grantwparks
5
Você deve inserir uma breakinstrução assim que o valor for encontrado.
Adel
4
@alex Tenha muito cuidado ao substituir array_merge()pelo +operador de matriz . Ele mescla por chave (também para teclas numéricas) e da esquerda para a direita , enquanto array_mergemescla da direita para a esquerda e nunca substitui as teclas numéricas. Por exemplo, [0,1,2]+[0,5,6,7] = [0,1,2,7]enquanto array_merge([0,1,2],[0,5,6,7]) = [0,1,2,0,5,6,7]e ['a' => 5] + ['a' => 7] = ['a' => 5]mas array_merge(['a' => 5], ['a' => 7]) = ['a' => 7].
gripe
É seguro usar o +sinal?
crmpicco
47

Que tal esta solução

$order = array(1,5,2,4,3,6);

$array = array(
    1 => 'one',
    2 => 'two',
    3 => 'three',
    4 => 'four',
    5 => 'five',
    6 => 'six'
);

uksort($array, function($key1, $key2) use ($order) {
    return (array_search($key1, $order) > array_search($key2, $order));
});
Peter de Groot
fonte
1
Este precisa de mais votos, outro não funcionou / não funciona bem enquanto isso funciona bem no meu caso.
Sek Long
Isso não é muito eficiente. Para cada comparação, são realizadas duas pesquisas lineares na matriz. Se assumirmos que a complexidade de tempo de uksort () é O(n * log n), esse algoritmo é executado O(n^2 * log(n)).
TheOperator 23/01
36

Outra maneira para o PHP> = 5.3.0:

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$customerSorted = array_replace(array_flip(array('name', 'dob', 'address')), $customer);

Resultado:

Array (
  [name] => Tim
  [dob] => 12/08/1986
  [address] => 123 fake st
  [dontSortMe] => this value doesnt need to be sorted
)

Funciona bem com string e teclas numéricas.

ekuser
fonte
2
Enquanto os dois trabalham, eu descobri que array_replace()a intenção é melhor do que array_merge().
Jason McCreary
1
array_replacetambém deixa o tipo de variável intacto. Se um dos valores em sua matriz tivesse sido (string) '1'e você tivesse usado o +operador, o valor teria sido transformado em(int) 1
halfpastfour.am
1
Isso também funciona em teclas numéricas ( array_merge()seria apenas anexá-las?). A lógica é muito bem explicada aqui . Primeiro , array_flip()altera os valores da matriz $ order para chaves. Segundo , array_replace()substitui os valores da primeira matriz por valores com as mesmas chaves na segunda matriz. Se você precisar classificar uma matriz de acordo com as chaves de outra, nem precisará usá-la array_flip.
Aexl
23
function sortArrayByArray(array $toSort, array $sortByValuesAsKeys)
{
    $commonKeysInOrder = array_intersect_key(array_flip($sortByValuesAsKeys), $toSort);
    $commonKeysWithValue = array_intersect_key($toSort, $commonKeysInOrder);
    $sorted = array_merge($commonKeysInOrder, $commonKeysWithValue);
    return $sorted;
}
OIS
fonte
14

Tome uma matriz como seu pedido:

$order = array('north', 'east', 'south', 'west');

Você pode classificar outra matriz com base nos valores usando o array_intersectDocumentos :

/* sort by value: */
$array = array('south', 'west', 'north');
$sorted = array_intersect($order, $array);
print_r($sorted);

Ou, no seu caso, para classificar por chaves, use o array_intersect_keyDocumentos :

/* sort by key: */
$array = array_flip($array);
$sorted = array_intersect_key(array_flip($order), $array);
print_r($sorted);

Ambas as funções manterão a ordem do primeiro parâmetro e retornarão apenas os valores (ou chaves) da segunda matriz.

Portanto, para esses dois casos padrão, você não precisa escrever uma função por conta própria para realizar a classificação / reorganização.

hakre
fonte
A interseção se livraria das entradas que ele não conhece antecipadamente.
9133 DanMan
1
Isso está incorreto para classificar por chaves. array_intersect_key retornará apenas os valores do array1, não do array2
spooky
Concordou com pavsid - o exemplo array_intersect_key está incorreto - retorna os valores da primeira matriz, não da segunda.
Jonathan Aquino
10

Usei a solução do Darkwaltz4, mas usei, em array_fill_keysvez de array_flip, para preencher NULLse uma chave não estiver definida $array.

$properOrderedArray = array_replace(array_fill_keys($keys, null), $array);
Baptiste Bernard
fonte
5

Sem magia ...

$array=array(28=>c,4=>b,5=>a);
$seq=array(5,4,28);    
SortByKeyList($array,$seq) result: array(5=>a,4=>b,28=>c);

function sortByKeyList($array,$seq){
    $ret=array();
    if(empty($array) || empty($seq)) return false;
    foreach($seq as $key){$ret[$key]=$dataset[$key];}
    return $ret;
}
Jenovai Matyas
fonte
1
Isso funciona agradáveis graças, apenas atualização $datasetde nome de parâmetro jogo
kursus
3

Se você tiver um array em seu array, precisará adaptar um pouco a função de Eran ...

function sortArrayByArray($array,$orderArray) {
    $ordered = array();
    foreach($orderArray as $key => $value) {
        if(array_key_exists($key,$array)) {
                $ordered[$key] = $array[$key];
                unset($array[$key]);
        }
    }
    return $ordered + $array;
}
Boombastic
fonte
2

Esta função retorna uma matriz sub e classificada com base no segundo parâmetro $ keys

function array_sub_sort(array $values, array $keys){
    $keys = array_flip($keys);
    return array_merge(array_intersect_key($keys, $values), array_intersect_key($values, $keys));
}

Exemplo:

$array_complete = [
    'a' => 1,
    'c' => 3,
    'd' => 4,
    'e' => 5,
    'b' => 2
];

$array_sub_sorted = array_sub_sort($array_complete, ['a', 'b', 'c']);//return ['a' => 1, 'b' => 2, 'c' => 3];
Doglas
fonte
1

O PHP tem funções para ajudá-lo com isso:

$arrayToBeSorted = array('west', 'east', 'south', 'north');
$order = array('north', 'south', 'east', 'west');

// sort array
usort($arrayToBeSorted, function($a, $b) use ($order){
    // sort using the numeric index of the second array
    $valA = array_search($a, $order);
    $valB = array_search($b, $order);

    // move items that don't match to end
    if ($valA === false)
        return -1;
    if ($valB === false)
        return 0;

    if ($valA > $valB)
        return 1;
    if ($valA < $valB)
        return -1;
    return 0;
});

Usort faz todo o trabalho para você e array_search fornece as chaves. array_search () retorna false quando não consegue encontrar uma correspondência para que os itens que não estão na matriz de classificação se movam naturalmente para a parte inferior da matriz.

Nota: uasort () ordenará a matriz sem afetar os relacionamentos chave => valor.

danielcraigie
fonte
1
  • classificar conforme solicitado
  • salvar para chaves int (por causa de array_replace)
  • chaves de retorno não existem no inputArray
  • (opcionalmente) chaves de filtro inexistentes em keyList especificadas

Código:

 /**
 * sort keys like in key list
 * filter: remove keys are not listed in keyList
 * ['c'=>'red', 'd'=>'2016-12-29'] = sortAndFilterKeys(['d'=>'2016-12-29', 'c'=>'red', 'a'=>3 ]], ['c', 'd', 'z']){
 *
 * @param array $inputArray
 * @param string[]|int[] $keyList
 * @param bool $removeUnknownKeys
 * @return array
 */
static public function sortAndFilterKeys($inputArray, $keyList, $removeUnknownKeys=true){
    $keysAsKeys = array_flip($keyList);
    $result = array_replace($keysAsKeys, $inputArray); // result = sorted keys + values from input + 
    $result = array_intersect_key($result, $inputArray); // remove keys are not existing in inputArray 
    if( $removeUnknownKeys ){
        $result = array_intersect_key($result, $keysAsKeys); // remove keys are not existing in keyList 
    }
    return $result;
}
Grão
fonte
1

Primeira sugestão

function sortArrayByArray($array,$orderArray) {
    $ordered = array();
    foreach($orderArray as $key) {
        if(array_key_exists($key,$array)) {
            $ordered[$key] = $array[$key];
            unset($array[$key]);
        }
    }
    return $ordered + $array;
}

Segunda Sugestão

$properOrderedArray = array_merge(array_flip(array('name', 'dob', 'address')), $customer);

Eu queria ressaltar que essas duas sugestões são incríveis. No entanto, são maçãs e laranjas. A diferença? Um é não-associativo e o outro é associativo. Se você estiver usando duas matrizes totalmente associativas, a mesclagem / inversão da matriz irá realmente mesclar e substituir a outra matriz associativa. No meu caso, esse não é o resultado que eu estava procurando. Eu usei um arquivo settings.ini para criar minha matriz de ordem de classificação. A matriz de dados que eu estava classificando não precisava ser substituída por minha contraparte de classificação associativa. Assim, a mesclagem de matriz destruiria minha matriz de dados. Ambos são ótimos métodos, ambos precisam ser arquivados em qualquer caixa de ferramentas de desenvolvedores. Com base nas suas necessidades, você pode achar que realmente precisa dos dois conceitos em seus arquivos.

user1653711
fonte
1

Adotei a resposta do @ Darkwaltz4 por sua brevidade e gostaria de compartilhar como adaptei a solução a situações em que a matriz pode conter chaves diferentes para cada iteração, da seguinte forma:

Array[0] ...
['dob'] = '12/08/1986';
['some_key'] = 'some value';

Array[1] ...
['dob'] = '12/08/1986';

Array[2] ...
['dob'] = '12/08/1986';
['some_key'] = 'some other value';

e manteve uma "chave mestra" assim:

$master_key = array( 'dob' => ' ' ,  'some_key' => ' ' );

array_merge teria executado a mesclagem na iteração Array [1] com base em $ master_key e produzido ['some_key'] = '', um valor vazio, para essa iteração. Portanto, array_intersect_key foi usado para modificar $ master_key em cada iteração da seguinte forma:

foreach ($customer as $customer) {
  $modified_key = array_intersect_key($master_key, $unordered_array);
  $properOrderedArray = array_merge($modified_key, $customer);
}
Pageii Studio
fonte
0

Um pouco tarde, mas não consegui encontrar a maneira como a implementei, esta versão precisa ser fechada, php> = 5.3, mas pode ser alterada para não:

$customer['address'] = '123 fake st';
$customer['name'] = 'Tim';
$customer['dob'] = '12/08/1986';
$customer['dontSortMe'] = 'this value doesnt need to be sorted';

$order = array('name', 'dob', 'address');

$keys= array_flip($order);
uksort($customer, function($a, $b)use($keys){
    return $keys[$a] - $keys[$b];
});
print_r($customer);

É claro que 'dontSortMe' precisa ser resolvido e pode aparecer primeiro no exemplo

DJules
fonte