Składanie kolekcji
Składanie list (ang. list comprehensions) to lukier składniowy, pozwalający na wygodniejszą pracę z kolekcjami i danymi typu wyliczeniowego. W tej lekcji przyjrzymy się temu, jak możemy użyć tego mechanizmu do iterowania po kolekcjach i generowania ich.
Podstawy
Najczęściej składanie list używane jest do tworzenia bardziej zwięzłego kodu do iterowania w Enum
oraz Stream
.
Przyjrzyjmy się prostemu przykładowi składania kolekcji, a następnie omówmy jego elementy:
iex> list = [1, 2, 3, 4, 5]
iex> for x <- list, do: x*x
[1, 4, 9, 16, 25]
Pierwsze co możemy zauważyć, to użycie słowa kluczowego for
i generatora.
Czym jest generator?
Generatorem nazywamy wyrażenie x <- [1, 2, 3, 4]
znajdujące się w liście składanej.
Odpowiada ono za generowanie kolejnych wartości.
Oczywiście składanie można stosować nie tylko do list, ale do wszystkich danych typu wyliczeniowego (ang. enumerables).
# Listy asocjacyjne
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]
# Mapy
iex> for {k, v} <- %{"a" => "A", "b" => "B"}, do: {k, v}
[{"a", "A"}, {"b", "B"}]
# Dane binarne
iex> for <<c <- "hello">>, do: <<c>>
["h", "e", "l", "l", "o"]
Jak zapewne zauważyłeś, generatory opierają się o dopasowanie wzorców, by przypisać dane do zmiennej po lewej stronie. Jeżeli jakiś element nie zostanie dopasowany, to jest po prostu ignorowany:
iex> for {:ok, val} <- [ok: "Hello", error: "Unknown", ok: "World"], do: val
["Hello", "World"]
Możliwe jest użycie wielu generatorów naraz, co może trochę przypominać zagnieżdżone pętle:
iex> list = [1, 2, 3, 4]
iex> for n <- list, times <- 1..n do
...> String.duplicate("*", times)
...> end
["*", "*", "**", "*", "**", "***", "*", "**", "***", "****"]
By lepiej zilustrować to zachowanie, użyjmy IO.puts
, by wyświetlić wartości z dwóch generatorów:
iex> for n <- list, times <- 1..n, do: IO.puts "#{n} - #{times}"
1 - 1
2 - 1
2 - 2
3 - 1
3 - 2
3 - 3
4 - 1
4 - 2
4 - 3
4 - 4
Składanie list to tzw. lukier składniowy i powinno być stosowane tylko wtedy, kiedy faktycznie jest potrzebne.
Filtry
O filtrach możemy myśleć jak o strażnikach dla składania list.
Gdy filtr zwraca wartość false
lub nil
, wartość ta jest wyłączana z ostatecznej listy.
Przeiterujmy po liście, biorąc pod uwagę jedynie liczby parzyste.
By sprawdzić parzystość liczby, użyjemy funkcji is_even/1
z modułu Integer:
import Integer
iex> for x <- 1..10, is_even(x), do: x
[2, 4, 6, 8, 10]
Filtry, podobnie jak generatory, możemy łączyć. Rozszerzmy nasz zakres liczb, a następnie przefiltrujmy go tak, by znaleźć liczby, które są jednocześnie parzyste i podzielne przez 3:
import Integer
iex> for x <- 1..100,
...> is_even(x),
...> rem(x, 3) == 0, do: x
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]
Użycie :into
Co jeżeli chcemy otrzymać coś innego niż listę?
Po to mamy opcję :into
!
Ogólna zasada jest taka, że :into
akceptuje jako argument dowolną strukturę implementującą protokół Collectable
.
Użyjmy:into
, by stworzyć mapę z listy asocjacyjnej:
iex> for {k, v} <- [one: 1, two: 2, three: 3], into: %{}, do: {k, v}
%{one: 1, three: 3, two: 2}
Jako że binarne ciągi znaków są kolekcjami, możemy użyć składania w połączeniu z :into
by stworzyć ciąg znaków:
iex> for c <- [72, 101, 108, 108, 111], into: "", do: <<c>>
"Hello"
I to wszystko! Składanie list jest mechanizmem pozwalającym na tworzenie zwięzłego kodu do obsługi kolekcji.
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!