Quem já usou Python já usou quase certamente compreensões de listas, que estão no centro da sintaxe de Python desde as primeiras versões.
Algo tão prático acaba por chegar a outras linguagens, incluindo Julia.
Para quem programa em Julia, as compreensões são uma opção e não uma necessidade, pois normalmente há alternativas (broadcasting, funções de ordem superior, etc.).
No entanto, uma compreensão oferece muitas vezes uma forma simples, legível e eficiente de construir um array. No fim de contas, usá-las ou não é uma questão de gosto pessoal, e da tua preferência por Python ou pelas linguagens funcionais.
A sintaxe é, na sua maioria, uma cópia direta da de Python, mas com extensões para arrays com mais dimensões.
Muito ao estilo de Python, incluindo a cláusula if opcional.
julia> [x^2 for x in 1:3]
3-element Vector{Int64}:
1
4
9
julia> [x^2 for x in 1:3 if isodd(x^2)]
2-element Vector{Int64}:
1
9
A forma da saída depende dos detalhes da compreensão: normalmente é a mesma da coleção de entrada em casos simples, mas uma cláusula if achata-a num Vector.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x^2 for x in m]
2×3 Matrix{Int64}:
1 4 9
16 25 36
julia> [x^2 for x in m if isodd(x^2)]
3-element Vector{Int64}:
1
25
9
Tal como em Python, podemos ter várias cláusulas for com variáveis diferentes, com coleções de entrada iguais ou diferentes.
julia> [x * y for x in 1:3 for y in 4:6]
9-element Vector{Int64}:
4
5
6
8
10
12
12
15
18
julia> [x * y for x in 1:3 for y in 4:6 if isodd(x * y)]
2-element Vector{Int64}:
5
15
Isto é equivalente a ciclos aninhados. A saída é unidimensional, mesmo quando a entrada é uma matriz.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x*y for x in m for y in m]
36-element Vector{Int64}:
1
4
2
5
(truncated output...)
A secção anterior descrevia várias cláusulas for separadas apenas por espaços.
Nesta secção há um único for e as variáveis são separadas por vírgulas.
julia> [(x, y) for x in 1:3, y in 4:6]
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
Cada variável da compreensão cria uma nova dimensão na saída, com uma entrada para cada combinação possível das variáveis.
No exemplo acima, x cresce à medida que descemos as linhas e y cresce ao longo das colunas.
Dimensões superiores são possíveis, com os avisos habituais sobre a legibilidade da saída.
As secções anteriores centraram-se na saída em array, com a compreensão colocada entre parênteses retos [ ... ].
Quando substituímos os parênteses retos por parênteses curvos ( ... ), acontece algo diferente: obtemos uma generator expression: um iterador de avaliação preguiçosa que produz o valor seguinte à medida que lhe é pedido.
julia> g = ((x, y) for x in 1:3, y in 4:6)
Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, var"#9#10"}(var"#9#10"(), Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:3, 4:6)))
# Indexing fails with a generator, the entries don't exist yet
julia> g[1, 2]
ERROR: MethodError: no method matching getindex(::Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{…}, UnitRange{…}}}, var"#9#10"}, ::Int64, ::Int64)
# conversion to array
julia> collect(g)
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
# generators are mainly designed for iteration
julia> [i * j for (i, j) in g]
3×3 Matrix{Int64}:
4 5 6
8 10 12
12 15 18
Quando o resultado de uma compreensão é usado de imediato em processamento posterior, uma expressão geradora pode ser eficiente em termos de memória, evitando a necessidade de guardar um array intermédio grande.
Uma expressão geradora usada como argumento de uma função não precisa de parênteses adicionais.
# inefficient
julia> v = [x^2 for x in 1:1e6];
julia> sum(v)
3.333338333335e17
# better - use a generator
julia> sum(x^2 for x in 1:1e6)
3.3333383333312755e17
A sintaxe das expressões geradoras é idêntica à das compreensões, à exceção dos parênteses que as rodeiam.
As expressões geradoras também podem ser úteis em construtores de dicionários.
julia> Dict(x => x^2 for x in 1:5)
Dict{Int64, Int64} with 5 entries:
5 => 25
4 => 16
2 => 4
3 => 9
1 => 1
Trabalhas numa loja de moda online. Tens a ideia de uma funcionalidade do site que sugere conjuntos ao utilizador. Interessam-te os conjuntos possíveis, os respetivos preços e eliminar as opções que colidem.
Embora possa haver diferentes formas de resolver os problemas seguintes, todos podem ser resolvidos com uma compreensão.
Implementa clothingitem() para receber dois Tuple, um de categories e outro das respetivas qualities.
A função deve devolver um Dict com as categories como chaves e as qualities como valores.
julia> categories = ("item_name", "price", "color", "base_color");
julia> qualities = ("Descriptive Name", 99.00, "Ochre Red", "red");
julia> clothingitem(categories, qualities)
Dict{String, Any} with 4 entries:
"price" => 99.0
"item_name" => "Descriptive Name"
"base_color" => "red"
"color" => "Ochre Red"
A tua função deve conseguir lidar com qualquer número de categories.
Implementa get_combinations() para receber um Tuple de partes de cima e um Tuple de partes de baixo.
A função deve devolver o cartesian product dos tuples como um array 2-D (ou seja, uma Matrix).
Cada entrada é uma combinação (top, bottom) como um Tuple.
As partes de cima mudam ao longo das linhas, as partes de baixo mudam ao longo das colunas.
julia> tops = (
Dict("item_name" => "Dress shirt"),
Dict("item_name" => "Casual shirt")
);
julia> bottoms = (
Dict("item_name" => "Jeans"),
Dict("item_name" => "Dress trousers")
);
julia> get_combinations(tops, bottoms)
2×2 Matrix{Tuple{Dict{String, String}, Dict{String, String}}}:
(Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Dress trousers"))
(Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Dress trousers"))
Cada peça de roupa tem um campo price associado, por isso pode ser útil conseguir comparar o preço total dos conjuntos.
Implementa get_prices(), que recebe um array de combinações de peças de roupa.
A função deve devolver um array com uma forma semelhante, com os preços dos conjuntos nas respetivas posições.
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combomatrix = get_combinations(tops, bottoms);
julia> get_prices(combomatrix)
2×2 Matrix{Int}::
65 110
50 95
julia> filteredmatrix = filter_clashing(combomatrix)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))
julia> get_prices(filteredmatrix)
1-element Vector{Int}:
50
Cada peça de roupa tem um campo base_color.
Usa este campo para remover todas as combinações em que a parte de cima e a parte de baixo têm a mesma cor base.
Implementa filter_clashing() para receber um array de combinações de peças de roupa.
Devolve um array 1-D (ou seja, um Vector) com as combinações correspondentes.
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combos = get_combinations(tops, bottoms);
julia> filter_clashing(combos)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))
Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.