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é!!