PHP ARRAY Parsing String Como Array Bidimensional no Hacker Rank

//Hacker Rank Input
//1 10 11 9 8 7 6 5 2 8 12, 7 58 2 34 76 88 2 5 3 8 11, 9 65 3 8 98 36 22 87 66 99 0

/* Read and parse input */
$handle = fopen ('php://stdin', 'r');
$string_array = fgets($handle);

$array = explode(', ', $string_array);

array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// do your job with bidimensional $array

Via de regra, quando estamos no ambiente Hacker Rank, para obtermos um array de um enunciado de um problema em PHP temos que fazer o parsing de uma string dada as características do input pelo STDIN.

As coisas podem se complicar bastante se o parsing não for bem executado, e é claro que com a pressão da competição, é o que geralmente acontece.

O problema não é do problema em si, por que depois que passa a competição e a pressão você olha pra ele e diz… PQP!

Então fica aí a dica, basta observar com calma, e principalmente observar os delimitadores. Neste caso o primeiro delimitador não é simplesmente uma vírgula, mas sim dois caracteres vírgula e espaço combinados.

# wrong delimiter applied on first level 
$array = explode(',', $string_array);

// [0]=> string(24) "1 10 11 9 8 7 6 5 2 8 12"
// [1]=> string(26) " 7 58 2 34 76 88 2 5 3 8 11"
// [2]=> string(29) " 9 65 3 8 98 36 22 87 66 99 0

Deste ponto em diante as coisas se complicam, pois apesar de termos um array unidimensional de três elementos, o próximo explode vai gerar o segundo nível com mais elementos do que o esperado por conter um espaço a mais.

# wrong quantity of elements on second level
array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// [0]=> array(11)
// [1]=> array(12)
// [2]=> array(12)

Agora não tem mais desculpas, essa é pra não esquecer mais.

Dang it!

PHP Array Combinando a Dupla Key Value como String

function getArgsAsString(array $args): string {
    return implode(', ', array_map(static function($v, $k){ return $k.'='.$v; }, $args, array_keys($args)));
}

Oxxê! Até o título deste artigo ficou estranho, mas primeiro vou explicar onde eu precisei usar isso pra ver se facilita o entendimento do porquê esse código pode ser útil pra você.

Quando escrevemos queries SQL em PHP é bastante comum fazermos o bind dos argumentos por nome associativo ao invés de posição. Essa técnica facilita não só a legibilidade do código como também ajuda a manter um código limpo e enxuto.

Pois bem, em um dado momento eu me recordo que precisei registrar um log da operação de SQL, daí além de registrar a string contendo a Query Statement, precisei também salvar os argumentos utilizados na operação. Foi neste momento que me dei conta de que o PHP não fornece uma solução nativa para este caso.

Rapidamente pensei nas funções nativas de parsing de Array do PHP, então o array_map caiu como uma luva. Lembrando que o array_map despreza os índices associativos do array original produzindo um novo array com índices sequenciais, mas como meu objetivo era produzir uma string com a combinação KEY=VALUE, eu não tive problemas com este fato.

Porém, é possível fazermos a mesma operação preservando os índices associativos originais do array, para isto basta usar a sintaxe abaixo com o comando array_walk.

array_walk($mapped_row, function(&$value, $key) {
    $value = sprintf('%s=%s', $value, $key);
}); 

$args_string = implode(', ', $args);

Lembrando que array_walk atua sobre o array original modificando o mesmo, diferentemente do array_map que produz um array completamente novo. Se você tiver problemas em modificar o seu array original crie então uma função passando o array como parâmetro.

Diferentemente de Objetos, os Arrays não são passados por referência por default, assim uma nova array será criada antes do parsing do array_walk, o que irá preservar o array original intacto.

function getArgsAsString(array $args): string {
    array_walk($args, function(&$value, $key) {
        $value = sprintf('%s=%s', $value, $key);
    });
    
    return implode(', ', $args);
}

Bom, fora este detalhe você pode ficar a vontade para utilizar a sintaxe que preferir… aproveite!

PHP Array Walk Sanitizar Objeto de forma recursiva

function sanitizeObject($object): void {
  array_walk($object, function($value, $key) use($object) {
    if (is_object($value) || is_array($value)) {
      self::sanitizeObject($value);
    }

    if ($value === null
        || (is_string($value) && empty($value))
        || (is_array($value) && !$value)
        || (is_object($value) && !(array)$value)
    ) {
      unset($object->{$key});
    }
  });
}

Bom pessoal, ao invés de explicar o que este código faz vou tentar explicar qual era o problema que eu tinha de resolver, acho que dessa forma o artigo fará mais sentido pra quem estiver lendo.

Eu estava trabalhando numa integração entre sistemas e a API que recebia as informações era bastante sensível quanto ao conteúdo da request. Por exemplo, o comportamento da API poderia variar muito entre eu enviar uma propriedade com conteúdo NULL ou então com a propriedade com conteúdo vazio. Em ambos os casos o melhor cenário seria não enviar a propriedade na request.

Já no início recebi uma dica de usar o array_filter para eliminar as propriedades nulas ou vazias, como este código aqui por exemplo:

function clearNullFields($metadata) {
  return (object)array_filter((array)$metadata, static function ($value) {
    return !(null === $value || trim($value) === '');
  });
}

O array_filter até poderia resolver a questão, isso se o objeto tivesse um único nível. Mas não era esse o caso, uma request de API normalmente é composta por vários outros objetos em múltiplos níveis.

Depois de pesquisar um pouco me deparei com um artigo onde a solução era dada por um array_walk. Bingo!

Bastou esta centelha e um pouco de criatividade para adicionar a recursividade fazendo com que todas as propriedades nulas ou vazias fossem sistematicamente removidas com unset.

Até mesmo um objeto mais interno cujas propriedades eram todas nulas ou vazias acabou sendo completamente removido. Isto porque a recursividade resolve as remoções do nível mais baixo para o nivel mais alto.

Assim após remover todas as propriedades escalares de um objeto, ele mesmo será um objeto vazio. Consequentemente ao retornar da recursividade inferior o objeto estará vazio e será removido exatamente por este motivo.

Mas um aviso importante!

O array_walk opera sobre o objeto fornecido alterando suas características originais. Assim para não comprometer nenhum processo que utilizasse o objeto original a solução foi criar um objeto totalmente novo para ser sanitizado. Como?

$sanitize = \json_decode(\json_encode($original));
sanitizeObject($sanitize);

Fantástico, funciona melhor do que o programado!