# 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
<?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
<?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
<?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
<?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
<?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
<?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é!!**
