Artigos de Tecnologia e Negócios > Programação

Trabalhando com arrays em PHP

vinicius-dias
vinicius-dias

Imagem de destaque #cover

Um amigo meu me pediu para criar um programinha que leia os dados que ele gerou (em JSON)sobre a pontuação dos países em uma competição e faça algumas manipulações.

Como eu já tenho bastante experiência com PHP, decidir utilizá-lo como linguagem para essa tarefa.

O primeiro passo foi, claro, pedir os dados para meu amigo, e foi esse JSON que ele me enviou:


[
    {
        "pais": "Brasil",
        "medalhas": {
            "ouro": 3,
            "prata": 5,
            "bronze": 3
        }
    },
    {
        "pais": "Costa rica",
        "medalhas": {
            "ouro": 5,
            "prata": 4,
            "bronze": 4
        }
    },
    {
        "pais": "Estados unidos",
        "medalhas": {
            "ouro": 4,
            "prata": 3,
            "bronze": 5
        }
    },
    {
        "pais": "Trindade e tobago",
        "medalhas": {
            "ouro": 4,
            "prata": 3,
            "bronze": 4
        }
    }
]

Com isso, sabemos que temos uma lista de objetos literais em JavaScript, mas e em PHP? Como esses dados seriam representados?

Lendo JSON

Bom, a primeira coisa a fazermos é ler esse arquivo em JSON e transformá-lo em algum dado que a gente possa maniuplar em PHP.

Felizmente isso é muito fácil e tudo que precisamos fazer é chamar uma função chamada json_decode.


<?php
$dadosEmString = // conteúdo do arquivo json
$dadosEmJson = json_decode($dadosEmString);
var_dump($dadosEmJson);

Executando este código, temos a seguinte saída:

array(4) {
  [0]=>
  object(stdClass)#1 (2) {
    ["pais"]=>
    string(6) "Brasil"
    ["medalhas"]=>
    object(stdClass)#2 (3) {
      ["ouro"]=>
      int(3)
      ["prata"]=>
      int(5)
      ["bronze"]=>
      int(3)
    }
  }
  [1]=>
  object(stdClass)#3 (2) {
    ["pais"]=>
    string(10) "Costa rica"
    ["medalhas"]=>
    object(stdClass)#4 (3) {
      ["ouro"]=>
      int(5)
      ["prata"]=>
      int(4)
      ["bronze"]=>
      int(4)
    }
  }
  [2]=>
  object(stdClass)#5 (2) {
    ["pais"]=>
    string(14) "Estados unidos"
    ["medalhas"]=>
    object(stdClass)#6 (3) {
      ["ouro"]=>
      int(4)
      ["prata"]=>
      int(3)
      ["bronze"]=>
      int(5)
    }
  }
  [3]=>
  object(stdClass)#7 (2) {
    ["pais"]=>
    string(17) "Trindade e tobago"
    ["medalhas"]=>
    object(stdClass)#8 (3) {
      ["ouro"]=>
      int(4)
      ["prata"]=>
      int(3)
      ["bronze"]=>
      int(4)
    }
  }
}

Podemos notar que recebemos como retorno um array de objetos do tipo stdClass. Isso é como se fosse um objeto literal do JavaScript. É um tipo de objeto sem forma onde podemos adicionar quaisquer propriedades que quisermos.

Se nós adicionássemos um segundo atributo à função json_decode, poderíamos informar que ao invés de objetos do tipo stdClass nós gostaríamos de receber arrays associativos. Então basta mudar nosso código para:


<?php
$dadosEmString = // conteúdo do arquivo json
$dadosEmJson = json_decode($dadosEmString, true);
var_dump($dadosEmJson);

E desta forma todos aqueles objetos se tornam arrays associativos.

Bom, agora que já lemos os dados para um formato conhecido em PHP, vamos ver o que meu amigo me pediu pra fazer:

Número de itens em um array

A primeira tarefa é fácil. Temos um array, então basta realizarmos um foreach por ele, e a cada iteração, incrementar um contador.

Mas, espera... Isso parece bem complexo pra uma tarefa tão simples. Antes mesmo de digitar esse código fui dar uma pesquisada em alguma solução melhor e encontrei a função count.

Esta função faz exatamente o que queremos: Conta o número de itens em um array PHP. Então para entregarmos a primeira solicitação, basta fazer o seguinte:


$numeroDePaises = count($dadosEmJson);
echo "Número de países participantes: $numeroDePaises";

E a saída será:

Número de países participantes: 4

Alterando os dados de um array

A segunda tarefa também parece fácil. É só fazer um foreach e deixar o índice pais de cada item na lista em letra maiúscula, não é?

Testei aqui e vi que não. Quando a gente usa o foreach, uma cópia de cada item é criada para realizar a iteração. Então aqui nós temos 3 soluções:

Bom, parece que a terceira opção é interessante e a função que mapeia um array para um novo com valores modificados se chama array_map.

Ela recebe um array por parâmetro e aplica uma função em cada item para que as alterações sejam realizadas e retorna o item modificado. É exatamente o que queremos e o código utilizando ela fica assim:


// ...
$dadosEmJson = array_map(function ($item) {
    $item['pais'] = mb_convert_case($item['pais'], MB_CASE_UPPER);
    return $item;
}, $dadosEmJson);

Agora se executarmos var_dump($dadosEmJson); de novo, veremos que todos os países estão com letra maiúscula.

Ordenando um array

Bom, agora vamos à parte de ordenar. Eu aprendi na faculdade alguns algoritmos de ordenação, mas honestamente não me lembro muito bem deles. Então dei uma pesquisada rápida sobre como ordenar um array e achei a função sort do PHP.

Com ela, se passarmos um array por parâmetro o próprio PHP ordena pra gente. Basicamente assim:


<?php
$array = [3, 5, 2, 1, 4];
sort($array);
var_dump($array); // [1, 2, 3, 4, 5]

Beleza, parece uma boa solução, mas como o PHP vai saber que nossa regra de ordenação é pela quantidade de medalhas? Seria legal eu poder fazer como no array_map e passar uma função dizendo como quero ordenar.

O PHP não me desaponta nunca e pesquisando encontrei rapidinho a função pra isso. É a usort. Essa função funciona igualzinho à sort, mas podemos passar uma função informando a regra de ordenação.

Funciona assim:

Passamos uma função que recebe 2 parâmetros, que são 2 itens do nosso array. Se segundo nossa regra, o primeiro item passado deve vir primeiro, nós devemos retornar um número negativo (-1 por exemplo).

Se o segundo item passado por parâmetro deve vir primeiro na nossa ordenação, retornamos um número positivo (1, por exemplo). Se pela nossa regra for um empate e o PHP não precisar fazer nda, basta retornar 0.

Sei que parece um pouco complicado, mas no código vai ficar mais fácil de entender. Dá uma olhada:


usort($dadosEmJson, function ($item1, $item2) {
    if ($item1['medalhas']['ouro'] > $item2['medalhas']['ouro']) {
        return -1;
    }
    if ($item1['medalhas']['ouro'] < $item2['medalhas']['ouro']) {
        return 1;
    }
    return 0;
});

Com isso, já conseguimos ordenar pelo país que mais tem medalhas de ouro, mas essa não era nossa única regra. Pensando um pouco, dá pra ver que nosso código ia ficar cheio de ifs dessa forma, então fiz uma otimização no código e ficou assim:


usort($dadosEmJson, function ($item1, $item2) {
    return $item2['medalhas']['ouro'] - $item1['medalhas']['ouro'];
});

Agora parece que nosso código não vai ficar tão grande. Conseguiu entender essa lógica? Faz alguns testes aí e vamos continuar implementando a regra.

Falta agora verificar, se as medalhas de ouro forem iguais, ordenar por prata. Se forem iguais ainda, ordenar por bronze. No final, fica parecido com isso:


usort($dadosEmJson, function ($item1, $item2) {
    $medalhas1 = $item1['medalhas'];
    $medalhas2 = $item2['medalhas'];

    return $medalhas2['ouro'] - $medalhas1['ouro'] !== 0
        ? $medalhas2['ouro'] - $medalhas1['ouro']
        : ($medalhas2['prata'] - $medalhas1['prata'] !== 0
            ? $medalhas2['prata'] - $medalhas1['prata'] 
            : $medalhas2['bronze'] - $medalhas1['bronze']);
});

Essa parte do código é meio complicadinha de ler, então se você preferir, pode fazer suas refatorações, criar outras funções para auxiliar ou ler com mais calma e realizar seus testes, mas com ela, conseguimos implementar a ordenação desejada. :-D

Reduzindo um array a apenas um dado

Agora nós chegamos na última tarefa. Precisamos pegar todo esse array, realizar os cálculos e retornar o número de medalhas distribuídos nessa competição.

Falando de outra forma, precisamos reduzir um array ao número de medalhas.

Aqui a gente poderia de novo fazer um foreach e ir somando cada dado, mas seria um pouco mais trabalhoso do que precisa, já que mais uma vez o PHP nos dá uma bela de uma mãozinha com a função array_reduce.

Com ela nós passamos uma função que irá acumulando os valores desejados de cada item em um array, e no final reduz isso tudo a um valor.

Um exemplo mais simples de uso dessa função é somar os dados de um array que contenha números. Ex.:


<?php
$array = [1, 2, 3, 4, 5];
$valorInicial = 0;
$soma = array_reduce($array, function ($itemAtual, $valorAcumulado) {
    return $valorAcumulado + $itemAtual;
}, $valorInicial);

E com isso nós teríamos em $soma o valor de 15.

Agora que já ficou um pouco mais claro, vamos aplicar para o nosso caso. Basta somarmos todos os valores das medalhas assim:


$numeroDeMedalhas = array_reduce($dadosEmJson, function ($medalhasAcumuladas, $itemAtual) {
    $medalhasDoPais = array_reduce($itemAtual['medalhas'], function ($medalhasAcumuladasDoPaisAtual, $medalhasDoPaisAtual) {
        return $medalhasAcumuladasDoPaisAtual + $medalhasDoPaisAtual;
    }, 0);

    return $medalhasAcumuladas + $medalhasDoPais;
}, 0);

echo "Total de medalhas entregues: $numeroDeMedalhas";

E este código exibirá:

Total de medalhas entregues: 47

Filtrando um array

Bom, foi trabalhoso mas conseguimos implementar tudo o que meu amigo pediu. Acho que seria legal até pra gente praticar um pouco mais, entregar também uma função que filtra desses países apenas os que não tiverem espaço em seu nome.

No PHP temos uma função exatamente para isso, o array_filter. Essa função recebe um array e uma função que retorna se cada item deve ou não fazer parte do novo array filtrado.

Para isso, basta retornarmos true apenas se não encontrarmos nenhum espaço no nome do país. Com isso nosso código pode ficar assim:


$paisesComNomeSemEspaco = array_filter($dadosEmJson, function ($item) {
    return strpos($item['pais'], ' ') === false;
});

E executando var_dump($paisesComNomeSemEspaco); vemos que funciona, e temos apenas o Brasil. :-D

Resumindo...

Bom, foi bastante coisa que fizemos aqui, então dando uma recapitulada, o que nós fizemos foi:

  1. Ler uma string em json para um formato conhecido pelo PHP;
  2. Contar os itens de um array;
  3. Mapear um array para outro com alterações;
  4. Ordenar um array com regras complexas;
  5. Reduzir um array a um único valor (a soma, por exemplo);
  6. Filtrar um array baseado em algum critério que definirmos;
  7. Como colocar uma string em letras maiúsuclas com mb_convert_case;
  8. Procurar uma string dentro de outra com strpos.

Conclusão

Ufa... Quanta coisa, né!? Arrays em PHP são muito poderosos e nos dão muitas facilidades. Ainda há muitas outras funções que você pode conferir na documentação do PHP, mas já deixo aqui uma dica:

Arrays no PHP são otimizados para tudo e para nada ao mesmo tempo, ou seja, você pode fazer o que quiser com eles, mas se precisar de muita performance, é melhor procurar alternativas como as Estruturas de Dados ou dar uma olhada no Curso de Collections aqui da Alura.

Artigos de Tecnologia e Negócios > Programação