map и filter

Две функции map и filter часто используются с коллекциями и итераторами. Они хорошо знакомы тем, кто пришел из функциональных языков программирования. Коротко: map применяет указанную функцию к коллекции или нескольким коллекциям одинаковой длины:

julia> map( x->"String from integer: $x", [1,2,3] )
3-element Array{String,1}:
 "String from integer: 1"
 "String from integer: 2"
 "String from integer: 3"


julia> map( (x,y)->"$x and $y", [1,2,3], [4,5,6])
3-element Array{String,1}:
 "1 and 4"
 "2 and 5"
 "3 and 6"

Filter применяет указанную функцию к каждому элементу и, в зависимости от результата (true/false) включает элемент в результирующую последовательность:

julia> filter( x->x%2==0, [1,2,3,4,5,6] )
3-element Array{Int64,1}:
 2
 4
 6

Прочтите документацию по ним на досуге, а сейчас рассмотрим их с точки зрения ленивости/энергичности.

Чаще всего объем читаемых данных является решающим фактором при ответе на вопрос: загружать ли все данные в память или обрабатывать и освобождать их поэлементно. Поэтому, важно знать какие функции как себя ведут при обработке данных.

map

Например, функция map - энергичная, в том смысле, что "не ленивая". Она обрабатывает элементы итератора по одному, но не возвращает управления, пока они не кончатся. Я создам генератор, который, отдавая новый элемент i, выводит info(i), обработаю его функцией map, и попрошу первый элемент результата:

julia> ((info("release $i");i) for i in 1:3) |> _->map(x->(info(x);x*10),_)|>first
INFO: release 1
INFO: 1
INFO: release 2
INFO: 2
INFO: release 3
INFO: 3
10

Были обработаны все элементы, значит, независимо от количества реально запрошенных элементов, результатом map будет полностью инициализированная новая коллекция. Понятно, что если использовать map для удаления символов конца строки всех строк огромного файла во время чтения, то памяти не хватит. Такое поведение map связано с производительностью: map предлагается часто использовать для обработки коллекций, в частности, для математических операций над массивами и матрицами, и ленивая версия была бы слишком медленной.

Если вам требуется ленивое поведение получившегося объекта (вычисление элементов по необходимости), то можно использовать генератор:

julia> source = ((info("release $i");i) for i in 1:3)
Base.Generator{UnitRange{Int64},##9#10}(#9,1:3)

julia> ("I got $s" for s in source) |> first
INFO: release 1
"I got 1"

Как видно из примера выше, функция first попросила первый элемент, он сбыл сформирован ("I got 1") из первого материализовавшегося элемента генератора source (INFO: release 1). Вычисления остальных элементов не произошло.

filter

filter - "ленивая когда нужно" функция. Если на входе итератор - возвращается новый итератор типа Filter:

julia> ((info("release $i");i) for i in [1,2,3,4]) |> _->filter( i->(info(i);true) ,_ )
Filter{##223#226,Base.Generator{Array{Int64,1},##221#224}}(#223,Base.Generator{Array{Int64,1},##221#224}(#221,[1,2,3,4]))

Если попросить первый элемент, то:

julia> ((info("release $i");i) for i in [1,2,3,4]) |> _->filter( i->(info(i);true) ,_ ) |> first
INFO: release 1
INFO: 1
INFO: release 2
INFO: 2
1

Было реализовано два элемента исходного итератора: первый, потому что он был запрошен функцией first и второй - потому, что производному итератору нужно знать ответ на вопрос done. См. главу Итераторы.

Если на входе будет массив, или другая реализованная коллекция - то на выходе будет тоже коллекция:

[1,2,3,4] |> _->filter( i->(info(i);true) ,_ )
INFO: 1
INFO: 2
INFO: 3
INFO: 4
4-element Array{Int64,1}:
 1
 2
 3
 4

Если попросить первый элемент:

julia>  [1,2,3,4] |> _->filter( i->(info(i);true) ,_ ) |> first
INFO: 1
INFO: 2
INFO: 3
INFO: 4
1

, видно, что проверками фильтра проверены все элементы.

Итак, filter - "ленивая когда нужно" функция, и ее можно использовать для построчной проверки больших файлов.

results matching ""

    No results matching ""