Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds comprehensions.md translation #8

Merged
merged 2 commits into from
Jul 16, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions comprehensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Списковые выражения
---

# Списковые выражения

В Эликсир часто нужно пройти в цикле по `Enumerable`, для того чтобы произвести какие-то преобразования над данными и передать их дальше. Списковые выражения - особый синтаксический сахар для таких задач: они позволяют использовать данные со специальной формой `for`.

Например, чтобы получить из списка чисел список их квадратов, потребуется сделать следующее:

```elixir
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
```

Списковые выражения состоят из трех частей: генераторы, фильтры и `Collectable` структуры.

## Генераторы и фильтры

В выражении выше `n <- [1, 2, 3, 4]` является генератором. Он в прямом смысле генерирует значения, которые будут использоваться в списковом выражении. С правой стороны выражения могут находиться любые перечисления:

```elixir
iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]
```

Генераторы также поддерживают операцию соответствия шаблону с левой стороны выражения, все неподходящие значения будут просто проигнорированы. Для примера представим, что у нас есть список с ключевыми словами, где ключами могут быть атомы `:bad` или `:good`, а мы хотим посчитать квадраты значений для ключей `:good`:

```elixir
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
```

В качестве альтернативы соответствия шаблону можно использовать фильтры, чтобы выбрать какие-то конкретные значения. Скажем, нам нужны только числа, которые делятся на `3`:

```elixir
iex> multiple_of_3? = fn(n) -> rem(n, 3) == 0 end
iex> for n <- 0..5, multiple_of_3?.(n), do: n * n
[0, 9]
```

Списковые выражения пропускают только те значения, для которых фильтр возвращает `false` или `nil`, остальные значения обрабатываются.

Списковые выражения обычно представляют собой более короткий вариант реализации, чем их альтернативы с использованием функций из модулей `Enum` и `Stream`. Более того, списковые выражения также позволяют использовать сразу несколько входных данных. В следующем примере списковое выражение получает на вход список директорий и возвращает размер каждого файла внутри:

```elixir
dirs = ['/home/mikey', '/home/james']
for dir <- dirs,
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
File.stat!(path).size
end
```

Несколько генераторов могут использоваться для получения прямого произведения двух списков:

```elixir
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
```

Теперь рассмотрим получение Пифагоровых троек в качестве более сложного примера. Пифагорова тройка - набор из трех натуральных чисел, таких что `a*a + b*b = c*c`. Давайте создадим файл `triple.exs`:

```elixir
defmodule Triple do
def pythagorean(n) when n > 0 do
for a <- 1..n,
b <- 1..n,
c <- 1..n,
a + b + c <= n,
a*a + b*b == c*c,
do: {a, b, c}
end
end
```

Теперь в выполним в терминале:

```bash
iex triple.exs
```

```elixir
iex> Triple.pythagorean(5)
[]
iex> Triple.pythagorean(12)
[{3, 4, 5}, {4, 3, 5}]
iex> Triple.pythagorean(48)
[{3, 4, 5}, {4, 3, 5}, {5, 12, 13}, {6, 8, 10}, {8, 6, 10}, {8, 15, 17},
{9, 12, 15}, {12, 5, 13}, {12, 9, 15}, {12, 16, 20}, {15, 8, 17}, {16, 12, 20}]
```

Для больших чисел операции выше становятся очень дорогими, более того наш код выдает дубликаты: `{a, b, c}` представляет одинаковую тройку с `{b, a, c}`. Но текущее списковое выражение может быть значительно оптимизировано, таким образом, чтобы переменные из прошлого генератора использовались в следующем, заодно и от дубликатов избавимся:

```elixir
defmodule Triple do
def pythagorean(n) when n > 0 do
for a <- 1..n-2,
b <- a+1..n-1,
c <- b+1..n,
a + b + c <= n,
a*a + b*b == c*c,
do: {a, b, c}

IO.inspect({a, b, c})
end
end
```

Необходимо помнить, что все переменные, созданные внутри спискового выражения (внутри генераторов, фильтров или переменные внутри самого блока кода), недоступны извне.

## Генераторы битовых строк

Генераторы битовых строк тоже поддерживаются, они очень полезны, когда вам нужно итерироваться по потокам битовых строк. Следующий пример получает на вход список пикселей из двоичных данных и создает из него кортежи по три значения: красный, зеленый и синий соответственно:

```elixir
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
```

Генераторы битовых строк могут быть использованы вместе с обычными генераторами и фильтрами.

## Опция :into

В примерах выше все списковые выражения возвращали список в качестве результата. Однако, результат может быть преобразован к другому типу при помощи опции `:into`.

Скажем, генератор битовых строк может быть использован с опцией `:into`, чтобы убрать все пробелы из строки:

```elixir
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
```

Множества, словари и другие типы могут быть переданы в опции `:into`, главное, чтобы они реализовывали протокол `Collectable`.

Частый случай использования опции `:into` - модифицирование значений словаря без изменения ключей:

```elixir
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}
```

Еще один пример с использованием потоков. Так как встроенный модуль `IO` предоставляет нам возможность работать с потоками (а потоки реализуют оба протокола: `Enumerable` и `Collectable`), то с помощью списковых выражений можно очень легко реализовать следующую логику: пользователь вводит строку, она приводится к верхнему регистру и выводится обратно:

```elixir
iex> stream = IO.stream(:stdio, :line)
iex> for line <- stream, into: stream do
...> String.upcase(line) <> "\n"
...> end
```

Теперь можно ввести в консоль что угодно и увидеть, что то же самое, но в верхнем регистре, будет выведено обратно. Но, к сожалению, теперь `IEx` застрял в списковом выражении. Чтобы выйти, нужно будет нажать `Ctrl+C` дважды.