alyatek
Posted on April 13, 2024
Qual a maneira mais eficaz, tanto de escrita como de processamento, de alcançarmos um key by no PHP?
O problema
Na minha pequena carreira de programador (já estou perto dos 6 anos nisto), um dos maiores problemas que encontro, com certa frequ*ência, a trabalhar com arrays é o típico *key by - que em termos gerais consiste em utilizar um valor do array multidimensional como a chave para ele mesmo.
Um exemplo de um array que tipicamente podemos encontrar no dia a dia (embora reduzido para exemplificar).
<?php
$products = [
[
'id' => 437,
'name' => 'Sapatilhas Amarelas',
'price' => 3500,
'slug' => 'sapatilhas-amarelas'
],
[
'id' => 548,
'name' => 'Sapatilhas Azuis',
'price' => 7500,
'slug' => 'sapatilhas-azuis'
],
];
Agora independentemente do motivo, imaginemos que precisamos de encontrar um dos artigos através da chave slug
.
Encontrar um array baseado na key do valor
Existe duas formas comuns de alcançar isso, sendo a mais comum e amadora, efetuar um foreach até encontrarmos o valor que pretendemos - pode-se ver isto como efetuar um brute force attack ao array.
Outra abordagem é o array_filter, que de certa forma é um foreach que valida se um condição é verdade e decide excluir ou manter o registo do array que proporcionamos. A vantagem deste meio é que podemos atribuir o resultado diretamente a uma variável e usar uma função anónima ou um função de arrow.
A desvantagem é que se pretendemos obter só um 1 registo teremos que chamar a primeira posição do array - pois apenas foi removido do array os valores que não pretendemos.
Tipicamente podiamos fazer um foreach até encontrar o que queremos, da seguinte forma:
<?php
$slug = 'sapatilhas-azuis';
$store = [];
foreach($products as $product) {
if($product['slug'] === $slug) {
$store = $slug;
continue;
}
}
Usando o onlinephp.io para efetuar os benchmarks, com 50 000 registos no array, este metódo de efetuar um foreach demorou em média 0,0055
segundos o que podemos considerar como "nada".
Por outro lado se efetuarmos um array_filter com o mesmo número de artigos:
<?php
$slug = 'sapatilhas-azuis';
$store = [];
$filteredProducts = array_filter(
$products,
fn($product) => $product['slug'] === $slug
);
$product = $filteredProducts[0];
Conluimos que, infelizmente apesar de ser mais elegante não parece o mais eficiente, pois a média resulta em 0,0107
segundos, o que representa um acréscimo de ~50% de tempo.
Estes processos são insignificantes quando apenas queremos ir buscar uma key. Mas quando precisamos de repetir a busca no mesmo processo, e aumentamos a complexidade da condição, já começa a ser bastante custuoso tanto em termos de código como processamento desnecessário.
Qual a forma mais correta de efetuar o key by?
Podemos simplesmente dizer que o mais eficiente é fazer um loop ao array e colocar a propria key como o valor que pretendemos.
<?php
function keyBy(string $key, array $values): array {
$array = [];
foreach($values as $value){
// antes de cada associamente deviamos
// primeiro validar se a key existe
// no array disponibilizado
$array[$value[$key]] = $value;
}
return $array;
}
$formated = keyBy('slug', [ /* array de produtos */ ])
Este processo demorou em média 0,0123
segundos. Mais uma vez, nada de mais.
Resumidamente podemos concluir que este processo é o melhor e mais rápido deles todos, no qual até a framework Laravel utiliza este mecanismo para alcançar o keyBy - apesar de existir mais algumas validações para garantir consistência no resultado.
Isto é a solução mais correta quando temos a disponibilidade de ter uma função global para isto.
Mas nem sempre isso é o caso - recentemente estive a implementar uma funcionalidade num produto relativamente antigo, onde não me sinto à vontade de começar a criar funções a torto e a direito no código - então queria uma solução mais elegante/"bonita" que podesse repetir sem caso fosse necessário:
<?php
$products = [ /* ... */ ];
$keys = array_column($products, 'slug');
$array = array_combine($keys, $products);
$product = $array['sapatilhas-azuis'];
Esta solução tem como duração média 0,0068
segundos - significativamente mais rápido que o foreach.
Passando a explicar:
Com o array_column extraimos todos os valores que estão associados à key slug do array products.
De seguida tiramos todo o partido do array_combine que vai utilizar os valores do array_column
para associar esse resultado às keys do array dos produtos.
Mas há um pequeno inconveniente com esta solução! Ao contrário do foreach, no qual podemos ter um fallback caso o campo não exista no array, com esta solução não é possível.
A falta do valor irá resultar num missmatch de contagem de keys:
Fatal error: Uncaught ValueError: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements in...
Conclusão
Pode-se concluir que, a maneira mais correta é de facto utilizar um foreach
, mas se a maneira mais correta for uma impossibilidade - tanto em termos de rapidez de execução e desenvolvimento do programador - podemos utilizar a solução do array_combine
+ array_combine
- que é uma extremamente intuitiva e rápida de compreender.
Posted on April 13, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.