Re

Reduce em jq

2 exercícios

Sobre Reduce

Suponha que queremos somar um array de números. Existem muitas formas de atingir esse objetivo.

  • Em muitas linguagens, esse problema é expresso como um laço com uma variável acumuladora.

  • Esse problema pode ser escrito como uma função recursiva. Em pseudocódigo, teríamos algo assim.

    function Add(X, Sum=0):
      if X is empty then
        return Sum
      else
        return Add(rest(X), Sum + first(X))
      end
    end
    

    Esse método de dividir o problema em partes menores também pode ser descrito como "reduzir em direção ao caso base".

Reduce é uma forma de combinar todos os elementos de uma estrutura de dados em um único valor. O processo itera sobre a estrutura de dados, aplicando uma função a cada elemento para atualizar o resultado acumulado.

No jq, esse processo é implementado no filtro reduce. Em outras linguagens, ele pode ser chamado de "fold", "fold-left", "inject" ou "aggregate".

A expressão reduce do jq é assim.

reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
  • STREAM_EXPRESSION é um stream de itens, e cada um deles é armazenado na variável $var, por vez.
  • INITIAL_VALUE é o valor inicial do resultado acumulado (conhecido como "acumulador").
  • A UPDATE_EXPRESSION combina ("folds") o valor atual ($var) no acumulador.
    • No contexto dessa expressão, . é o valor do acumulador.
    • A saída da expressão é armazenada no acumulador para uso na próxima iteração.
    • Depois da última iteração, o resultado acumulado é a saída do reduce.

Vamos ver um exemplo: somar os números de um array. O filtro add faz exatamente isso, mas vamos ver como implementá-lo.

Se usarmos [10, 20, 30, 40] como entrada e tomarmos zero como estado inicial, cada passo fica assim.

# estado elemento redutor resultado
1 0 10 0 + 10 10
2 10 20 10 + 20 30
3 30 30 30 + 30 60
4 60 40 60 + 40 100

Na sintaxe do jq, isso fica como este código.

0 + 10 | . + 20 | . + 30 | . + 40

Podemos expressar isso com o filtro reduce.

[10, 20, 30, 40] | reduce .[] as $n (0; . + $n)     # => 100
Note

O add é um builtin que na verdade é implementado com reduce, mas usa "null" como estado inicial (qualquer tipo de dado pode ser somado a null).

def add: reduce .[] as $x (null; . + $x);

Algumas coisas para ter em mente

  • Na expressão de redução, . é o acumulador. Se a entrada for um objeto que você precisa referenciar dentro da função de redução, você precisa armazená-lo em uma variável.

    {"apple": 10, "banana": 16, "carrot": 4}
    | . as $obj
    | reduce (keys | .[]) as $key (0; . + $obj[$key])     # => 30
    
  • O acumulador pode ser de qualquer tipo de dado. Por exemplo, você pode querer inverter um array.

    ["A", "B", "C", "D"]
    | reduce .[] as $elem ([]; [$elem] + .)       # => ["D", "C", "B", "A"]
    
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Reduce

A prática está bloqueada

Desbloqueie mais 3 exercícios para praticar Reduce