Map With Filter Shortcut
elixir에서 Enum.map/2
하고 Enum.filter/2
를 붙여서 호출해야 하는 경우가 있는데, 가끔 귀찮을 때가 있다. map으로 변형을 하고 그걸 다시 filter를 하고 다시 map을 해야한다거나 하는식으로 복잡해진다거나 리스트를 여러번 순회하는게 부담스러운 경우. Stream
모듈을 써도 되고 그게 더 좋긴 하지만 다른 방법도 깨달은 김에 남겨놓는다.
Enum.map/2
대신 Enum.flat_map/2
을 사용하기
Enum.flat_map/2
은 Enum.map |> List.flatten
과도 같은데, List.flatten/1
은 말그대로 중첩 리스트를 평평하게 펴주는 것이다.
예를 들면 [[2], [3, 4], [5], []]
같은 목록이 있다면 [2, 3, 4, 5]
가 된다. 즉 map 고차함수의 결과가 list로 나오고 그것이 flat하게 되는것.
이 때 []
같은 빈 리스트가 자연스럽게 걸러지는걸 볼 수 있다.
이를 이용해 다음처럼 filter, map 을 합친 효과를 볼 수 있다.
# 다음 두 함수는 동일
def map_filter(input) do
input
|> Enum.filter(&rem(&1, 2) == 0)
|> Enum.map(& &1 * 2)
end
def flat_map(input) do
input
|> Enum.flat_map(fn
x when rem(x, 2) == 0 ->
[x * 2]
_ ->
[]
end)
end
다음은 아는 내용이지만 다시한번 정리.
대부분의 함수형 프로그래밍 언어에는 굉장히 자주 쓰이게 되는 map과 filter 함수가 있다. 리스트 형태 데이터를 input이라고 가정하면, map은 데이터를 1:1로 변경하여 같은 길이의 리스트가 나오고, filter는 데이터를 조건에 맞게 선별하여 본래 데이터 길이보다 적거나 같은 결과를 반환한다.
그런데 함수형 언어에서도 lazy한 언어냐 아니냐에 따라 위 두 함수의 활용이 조금 다를 수 있다. lazy한 언어에서는 보통 리스트 데이터가 일련의 함수 파이프라인을 통과할 때 각 함수마다 매번 리스트를 순회하지 않는다.
예를 들면 하스켈에서는 다음과 같다.
multiTwoIfEven :: [Int] -> [Int]
multiTwoIfEven = map (* 2) . filter even
이 함수에 만약 길이가 수십만 혹은 무한의 리스트를 넣더라도 하스켈은 모든 리스트를 filter하고 map 함수를 실행하는게 아니라 그 데이터에서 쓰이는 만큼만 일련의 함수들을 실행하여 꺼낼 것이다. 즉 다음처럼 호출하면
take 5 $ multiTwoIfEven [1..]
5개의 output이 나올때까지만 함수를 실행시킬 것이다.
elixir 에서는 lazy가 default가 아니라 eager하게 함수가 실행된다. 즉 filter 함수를 호출하면 전체 데이터를 순회하게 된다.
input
|> Enum.filter(&rem(&1, 2) == 0)
|> Enum.map(& &1 * 2)
lazy하게 실행하려면 Stream
모듈을 이용하면 된다. Stream
모듈은 Enum
모듈과 같은 함수들을 가지고 있다.
input
|> Stream.filter(&rem(&1, 2) == 0)
|> Stream.map(& &1 * 2)
|> Stream.run() # or you could just use `Enum.map/2` as last call.
Enum
모듈과 같은 함수 용법이지만 Enum
모듈과 다르게 Stream
모듈은 모든 데이터가 순회되지 않고 각 데이터마다 일련의 함수가 호출된다.