PHP para Iniciantes: Operadores de Arrays
Este é o antepenúltimo artigo sobre operadores, mas esses não são menos importantes do que todos que citamos anteriormente. Como você já sabe, array
é um tipo de dado primitivo que nos dá a possibilidade de lidar com diferentes tipos de conceitos:
- listas;
- dicionários;
- matrizes;
- conjuntos;
- etc.
Falando especificamente sobre conjuntos, percebemos a necessidade de algumas operações. Citando, por exemplo, duas ações comuns sobre esse tópico: união
e comparação
. Como você faria isso em um array?
Bom, algumas coisas já estão prontas pra você. Mas nem sempre tudo pode funcionar como você quer - e aí é bom saber como fazer certas coisas sem depender das "soluções prontas", confere?
Dito isso, para cada operador deste artigo, irei escrever uma função que faz a mesma coisa. Assim você estará pronto(a) para alternativas.
Operador +
Ué, Kiko, esse operador não é de soma?
Sim, entre números. Entre arrays, esse operador é de união. Ele é equivalente ao array_merge()
, exceto que, na união, os valores do array à esquerda são preservados, enquanto no merge, todo valor à direita sobreescreve o da esquerda.
Como assim, Kiko?!
Vamos comparar:
<?php
$redesOn = [
'um' => 'Twitter',
'dois' => 'TikTok',
'tres' => 'Kawaii'
];
$redesOff = [
'um' => 'Facebook',
'dois' => 'Instagram'
];
$resultado1 = $redesOn + $redesOff;
$resultado2 = array_merge($redesOn, $redesOff);
var_dump($resultado1); // array(um => Twitter, dois => TikTok, tres => Kawaii)
var_dump($resultado2); // array(um => Facebook, dois => Instagram, tres => Kawaii)
No caso de dicionários (arrays
de palavras-chave, ao invés de índices numéricos), é essa a diferença entre as duas operações. Enquanto a união manteve os valores de $redesOn
, o array_merge()
sobreescreveu os valores das chaves 'um'
e 'dois'
com os valores de $redesOff
. Ainda assim, o último (Kawaii
) foi adicionado em ambos os casos, pois só existia em $redesOn
.
E se fosse uma lista, Kiko?
Nesse caso, teríamos um comportamento diferente somente na função array_merge()
. A união continua mantendo os valores da esquerda onde as chaves são iguais. Por exemplo, se as duas listas possuem o índice 0
, somente o valor do índice 0
do array à esquerda é preservado.
Enquanto o array_merge()
puxa todos os valores do array à direita e coloca no final do array à esquerda:
<?php
$redesOn = ['Twitter', 'TikTok', 'Kawaii'];
$redesOff = ['Facebook', 'Instagram'];
$resultado1 = $redesOn + $redesOff;
$resultado2 = array_merge($redesOn, $redesOff);
var_dump($resultado1); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii)
var_dump($resultado2); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii, 3 => Facebook, 4 => Instagram)
Isso é um erro de design da linguagem, Kiko?
Absolutamente não. O operador é preciso: na união de A
com B
, eu pego tudo que B
tem que não está em A
e faço um grupo com os dois. Como raios o interpretador sabe o que não tem em A
? Buscando valores? Não, né?... Ele basicamente analisa os índices! "Se o índice existe, então A
já tem aquele dado".
Se fôssemos reescrever isso em estrutura de looping, seria algo mais ou menos assim:
<?php
function uniao(array $A, array $B): array
{
foreach($B as $indice => $valor) {
if (!isset($A[$indice])) {
// se $A não tem aquele $indice, seta ele
$A[$indice] = $valor;
}
}
return $A;
}
$redesOn = ['Twitter', 'TikTok', 'Kawaii'];
$redesOff = ['Facebook', 'Instagram'];
var_dump(uniao($redesOn, $redesOff)); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii)
var_dump($redesOn + $redesOff); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii)
Isso também vai funcionar com as chaves em string
, será o mesmo resultado. Como o comportamento é o mesmo perante ambos os casos, não podemos, jamais, dizer que isso é erro de design.
E o
array_merge()
, Kiko?
Aí é outra história! Tem um comportamento diferente pra cada caso, o que sinaliza um possível acúmulo de responsabilidade e até pode ser considerado erro de design. Mas não é nada tão grotesco assim, desde que você saiba com que tipo de dados está lidando. Para exemplificar melhor, vou fazer duas funções: uma para o merge de listas e outro para merge de dicionários. Ao final delas, farei a bendita função de merge com acúmulo de responsabilidade:
<?php
function mergeLista(array $listaA, array $listaB): array
{
foreach ($listaB as $valor) {
$listaA[] = $valor; // adiciona o elemento no fim da lista de $listA
}
return $listaA;
}
function mergeDicionario(array $dicA, array $dicB): array
{
foreach($dicB as $indice => $valor) {
$dicA[$indice] = $valor; // quer ele exista ou não
}
return $dicA;
}
// com essas duas funções, que operam de formas completamente diferentes, podemos fazer uma função merge similar ao array_merge
function mergeArrays(array $A, array $B): array
{
// comparações abaixo baseadas na definição da função 'array_is_list()' do PHP 8.1
// https://www.php.net/manual/en/function.array-is-list.php
// fiz assim pra poder rodar em PHP 7
$isListA = array_key_first($A) === 0;
$isListB = array_key_first($B) === 0;
if ($isListA && $isListB) {
return mergeLista($A, $B);
}
return mergeDicionario($A, $B);
}
// E aqui vamos nós:
// LISTAS
$redesOn = ['Twitter', 'TikTok', 'Kawaii'];
$redesOff = ['Facebook', 'Instagram'];
var_dump(mergeArrays($redesOn, $redesOff)); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii, 3 => Facebook, 4 => Instagram)
var_dump(array_merge($redesOn, $redesOff)); // array(0 => Twitter, 1 => TikTok, 2 => Kawaii, 3 => Facebook, 4 => Instagram)
// DICIONÁRIOS
$redesOn = [
'um' => 'Twitter',
'dois' => 'TikTok',
'tres' => 'Kawaii'
];
$redesOff = [
'um' => 'Facebook',
'dois' => 'Instagram'
];
var_dump(mergeArrays($redesOn, $redesOff)); // array(um => Facebook, dois => Instagram, tres => Kawaii)
var_dump(array_merge($redesOn, $redesOff)); // array(um => Facebook, dois => Instagram, tres => Kawaii)
Vendo essas implementações, você poderá saber qual operação se adequa melhor nos seus objetivos, certo? Se a ideia é juntar os valores, pensa no array_merge
. Se for para preservar as chaves e valores do primeiro array
, aí usa a união.
Operador ==
Esse é um comparador, certo, Kiko?
Mais exato que isso, só a matemática... E aqui tem a mesma função. Mas é bom ressaltar que, no caso de arrays, o PHP irá checar se todas as chaves que tem em um está presente no outro e se possuem o mesmo valor. Sendo assim, podemos reescrever esse comparador com um loop validando:
- se os tamanhos são iguais;
- se as chaves do primeiro existem no segundo;
- se o valor de cada chave do primeiro é igual ao valor da mesma chave no segundo.
Vamos reescrever?
<?php
function comparaArray(array $A, array $B): bool
{
if (count($A) !== count($B)) { // se tiver uma quantidade diferente, já dá false
return false;
}
foreach ($A as $indice => $valor) {
// como eles têm o mesmo tamanho, tanto faz se você varre $A ou $B
// o ideal é varrer um pra comparar com o outro
// no menor sinal de diferença, é return false
if (!isset($B[$indice])) { // esse índice não tem em $B? são diferentes
return false;
}
if ($B[$indice] != $valor) { // o valor de $A[$indice] é diferente em $B[$indice]? diferentes
return false;
}
}
// percorreu todo o array e não deu nada diferente? então é igual!
return true;
}
$redesOn = ['Twitter', 'TikTok', 'Kawaii'];
$redesOff = ['Facebook', 'Instagram'];
$redesOn2 = [
'um' => 'Twitter',
'dois' => 'Tiktok',
'tres' => 'Kawaii',
];
var_dump(comparaArray($redesOn, $redesOn)); // bool(true)
var_dump(comparaArray($redesOn, $redesOff)); // bool(false)
var_dump(comparaArray($redesOn, $redesOn2)); // bool(false)
var_dump($redesOn == $redesOn); // bool(true)
var_dump($redesOn == $redesOff); // bool(false)
var_dump($redesOn == $redesOn2); // bool(false)
Bom, não tem muito o que exemplificar na comparação de igualdade de arrays. O bicho pega é no de identidade...
Operador ===
Pois é, comparador de identidade é algo que já é estranho abordar com tipos de dados escalares, imagine array
... Aqui, as condições são:
- mesmo tamanho;
- mesmas chaves presentes;
- mesmos valores e tipos de dados nas mesmas chaves;
- chaves na mesma ordem.
Portanto, são apenas duas novidades perante o operador anterior: além de tudo aquilo, tem a checagem de tipo de dados e a ordem das chaves.
Como você checaria isso, Kiko?
Olha, tem muitas formas de fazer isso. Dá pra checar no mesmo loop, separando as chaves do array $B
e ir fazendo em sequência... Mas tenho certeza que você pode encontrar um jeito diferente: fica aí o desafio pra você lançar a braba nos comentários, rs. Bora?!
<?php
function comparaIdentidadeArray(array $A, array $B): bool
{
if (count($A) !== count($B)) { // se tiver uma quantidade diferente, já dá false
return false;
}
$chavesB = array_keys($B);
$chaveAtual = 0;
foreach ($A as $indice => $valor) {
// como eles têm o mesmo tamanho, tanto faz se você varre $A ou $B
// o ideal é varrer um pra comparar com o outro
// no menor sinal de diferença, é return false
if (!isset($B[$indice])) {
// esse índice não tem em $B? são diferentes
return false;
}
if ($B[$indice] !== $valor) {
// o valor de $A[$indice] não é idêntico em $B[$indice]? diferentes
return false;
}
if ($chavesB[$chaveAtual++] != $indice) {
// a chave de $B na ordem atual não é a mesma chave em $A? diferentes
return false;
}
}
// percorreu todo o array e não deu nada diferente? então é idêntico!
return true;
}
$A = [1, 2, 3, 4, 5];
$B = [1, 2, 3, 4, 5.0]; // valor float no final
$C = [0 => 1, 2 => 3, 1 => 2, 3 => 4, 4 => 5]; // troca na ordem das chaves 1 e 2
var_dump(comparaIdentidadeArray($A, $A)); // bool(true)
var_dump(comparaIdentidadeArray($A, $B)); // bool(false)
var_dump(comparaIdentidadeArray($A, $C)); // bool(false)
var_dump($A === $A); // bool(true)
var_dump($A === $B); // bool(false)
var_dump($A === $C); // bool(false)
// NOTA: a igualdade de $A, $B (não identidade), daria true, da mesma forma de $A com $C
var_dump($A == $B); // bool(true), ignora a diferença de float e int
var_dump($A == $C); // bool(true), ignora a desordenação da lista, os índices tem o mesmo valor
E agora você também conseguirá discernir quando deve usar a comparação de identidade ou apenas de igualdade entre arrays.
Operador <>
ou !=
A partir de agora, estaremos negando os operadores que criamos logo acima. Ou seja, a implementação deles é: em todos os casos que davam false
, retorna true
. E onde dava true
, retorna false
!
Esses primeiros representam a operação de desigualdade, ou seja, o que não é igual. Por tanto é o oposto do operador ==
, o que dispensa implementações, certo?
Operador !==
Esse é o operador que nega a identidade. No caso, é chamado de não identidade. É o oposto do operador ===
, o que também dispensa implementações.
E por hoje é só! Curtiu? Comenta e compartilha! E não esquece de contribuir com o desafio: implemente do seu jeito! No próximo artigo falaremos sobre Operador de Tipo. Está ansioso(a)? Então pode esperar!
Inté!!