Map (고차 함수)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
Map (고차 함수)는 주어진 함수를 입력 자료 구조의 각 요소에 적용하여 새로운 자료 구조를 생성하는 고차 함수이다. 기본적으로 입력 자료 구조와 동일한 크기 및 형태의 새로운 자료 구조를 반환하며, 함수형 프로그래밍 언어에서 유래되었다. 다양한 프로그래밍 언어에서 구현되며, C++, C#, Python, Ruby 등 절차적/객체 지향 언어에서도 매핑 함수 또는 유사한 기능을 제공한다. 맵 퓨전, 맵-폴드 퓨전 등의 최적화 기법을 통해 성능을 향상시킬 수 있으며, Haskell에서는 Functor 타입 클래스를 통해 일반화되어 다양한 데이터 구조에 적용될 수 있다.
매핑은 주어진 함수를 입력 자료 구조의 각 원소에 적용하여 새로운 값을 계산하고, 이를 모아 새로운 자료 구조를 생성하는 과정을 의미한다.
map 함수는 함수형 프로그래밍 언어에서 유래되었다. 오늘날에는 많은 절차적, 객체 지향, 다중 패러다임 언어에서도 지원되거나 정의될 수 있다.
2. 개념
예를 들어 정수 목록 `[1, 2, 3, 4, 5]`가 있고 각 정수의 제곱을 계산하려 한다고 가정해 보자. 이를 위해 먼저 단일 숫자를 제곱하는 함수를 정의한 뒤, `map` 함수를 사용하여 각 요소에 해당 함수를 적용할 수 있다.
2. 1. 기본 원리
`map` 함수는 정수 목록 `[1, 2, 3, 4, 5]`가 있을 때, 각 정수의 제곱을 계산하는 것과 같이 사용된다. 먼저, 단일 숫자를 제곱하는 함수를 정의한다. (다음은 Haskell로 나타낸 것이다.)
```haskell
square x = x * x
```
그런 다음 `map square [1, 2, 3, 4, 5]`를 호출하면 `[1, 4, 9, 16, 25]`가 생성된다. 이는 `map`이 목록 전체를 순회하며 각 요소에 `square` 함수를 적용했음을 보여준다.
위 그림은 정수 목록 `X = [0, 5, 8, 3, 2, 1]`에 대해 함수 에 따라 새로운 목록 `X'`으로 매핑하는 각 단계를 시각적으로 보여준다.
`map` 함수는 Haskell의 기본 전주곡(즉, "표준 라이브러리")의 일부로 제공되며 다음과 같이 구현된다.[1]
```haskell
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x : xs) = f x : map f xs
2. 2. 함수형 프로그래밍
매핑은 함수형 프로그래밍의 핵심 개념 중 하나이며, 부작용(side effect) 없이 자료를 처리하는 데 사용된다. 고차 함수(higher-order function)를 사용하여 함수를 인자로 받거나 결과로 반환할 수 있다.
정수 목록 `[1, 2, 3, 4, 5]`가 있고 각 정수의 제곱을 계산한다고 가정해 보자. 이를 위해 먼저 단일 숫자를 제곱하는 함수를 정의한다. (다음은 Haskell로 나타낸 것이다).
```haskell
square x = x * x
```
그런 다음 다음을 호출한다.
```haskell
>>> map square [1, 2, 3, 4, 5]
```
그러면 `[1, 4, 9, 16, 25]`가 생성되는데, 이는 `map`이 전체 목록을 거치면서 각 요소에 함수 `square`를 적용했음을 보여준다.
Haskell에서, 다형 함수 `map :: (a -> b) -> [a] -> [b]`는 다형적 함수 `fmap :: Functor f => (a -> b) -> f a -> f b`로 일반화되는데, 이는 `Functor` 타입 클래스에 속하는 모든 타입에 적용된다.
리스트의 타입 생성자 `[]`는 이전 예제의 `map` 함수를 사용하여 `Functor` 타입 클래스의 인스턴스로 정의할 수 있다.
```haskell
instance Functor [] where
fmap = map
```
`Functor` 인스턴스의 다른 예로는 트리가 있다.
```haskell
data Tree a = Leaf a | Fork (Tree a) (Tree a)
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Fork l r) = Fork (fmap f l) (fmap f r)
```
트리에 매핑하면 다음과 같은 결과가 생성된다.
```haskell
>>> fmap square (Fork (Fork (Leaf 1) (Leaf 2)) (Fork (Leaf 3) (Leaf 4)))
Fork (Fork (Leaf 1) (Leaf 4)) (Fork (Leaf 9) (Leaf 16))
```
`Functor` 타입 클래스의 모든 인스턴스에 대해, `fmap`은 펀터 법칙을 준수해야 한다.
```haskell
fmap id ≡ id -- 항등 법칙
fmap (f . g) ≡ fmap f . fmap g -- 합성 법칙
```
여기서 `.`는 Haskell의 함수 합성을 나타낸다.
이것은 무엇보다도 다양한 종류의 컬렉션에 대한 요소별 연산을 정의할 수 있게 해준다.
3. 언어별 구현 및 비교
Lisp는 1959년에 `maplist`라는 함수를 도입했으며,[3] 1958년에는 약간 다른 버전이 이미 등장했다.[4] Common Lisp와 같은 최신 Lisp에서는 `maplist` 대신 `mapcar` 또는 더 일반적인 `map` 함수를 사용하는 것이 일반적이다.[5]
C++의 표준 라이브러리에서는 `std::transform`, C# (3.0)의 LINQ 라이브러리에서는 `Select`라는 확장 메서드로 제공된다. 콜드퓨전 마크업 언어 (CFML), Perl, Python, Ruby와 같은 고급 언어에서도 `map`이라는 이름으로 자주 사용되며, Ruby에서는 `collect`라는 별칭도 제공된다 (Smalltalk에서 유래).[3]
2개 이상의 목록에서 해당하는 요소에 함수를 적용하는 기능(예: `map2`, `zipWith`)을 제공하는 언어도 있다. 목록의 길이가 다른 경우, 언어마다 처리 방식이 다르다. 예외를 발생시키거나, 가장 짧은 목록을 기준으로 중지하거나, 가장 긴 목록까지 진행하며 값이 없는 경우 특정 값을 전달하는 방식 등이 있다.[3]
1급 함수와 커링을 지원하는 언어에서 `map`은 단일 값에 작동하는 함수를 컨테이너 전체에 작동하는 함수로 만들 수 있다. 예를 들어, Haskell에서 `map square`는 목록의 각 요소를 제곱하는 함수가 된다.
다음 표는 다양한 프로그래밍 언어에서의 map 함수 구현 및 다중 목록 매핑 방식을 비교한 것이다.언어 Map 2개의 목록 Map n개의 목록 Map 비고 길이가 다른 목록 처리 APL func list
list1 func list2
func/ list1 list2 list3 list4
APL의 배열 처리 기능은 map과 같은 연산을 암묵적으로 수행한다. 목록 길이가 같지 않거나 1인 경우 길이 오류 Common Lisp (mapcar func list)
(mapcar func list1 list2)
(mapcar func list1 list2 ...)
가장 짧은 목록의 길이에서 중지 C++ std::transform(begin, end, result, func)
std::transform(begin1, end1, begin2, result, func)
헤더
begin, end, result는 반복자
result는 result에서 시작하여 쓰여짐C# ienum.Select(func)
`select` 절ienum1.Zip(ienum2, func)
Select
는 확장 메서드
ienum은 IEnumerableZip
은 .NET 4.0에서 도입됨
모든 .NET 언어에서도 유사하게 동작가장 짧은 목록이 종료되면 중지 CFML obj.map(func)
obj
가 배열 또는 구조체인 경우. func
는 각 항목의 값, 해당 인덱스 또는 키 및 원래 객체에 대한 참조를 인수로 받음.Clojure (map func list)
(map func list1 list2)
(map func list1 list2 ...)
가장 짧은 목록의 길이에서 중지 D list.map!func
zip(list1, list2).map!func
zip(list1, list2, ...).map!func
StoppingPolicy로 zip을 지정: shortest, longest, 또는 requireSameLength Erlang lists:map(Fun, List)
lists:zipwith(Fun, List1, List2)
zipwith3
도 사용 가능목록은 길이가 같아야 함 Elixir Enum.map(list, fun)
Enum.zip(list1, list2) |> Enum.map(fun)
List.zip([list1, list2, ...]) |> Enum.map(fun)
가장 짧은 목록의 길이에서 중지 F# List.map func list
List.map2 func list1 list2
다른 타입(Seq 및 Array)에 대한 함수가 존재함 예외 발생 Groovy list.collect(func)
[list1, list2].transpose().collect(func)
[list1, list2, ...].transpose().collect(func)
Haskell map func list
zipWith func list1 list2
zipWithn func list1 list2 ...
n
은 목록 수를 나타냄; zipWith7
까지 미리 정의됨가장 짧은 목록의 길이에서 중지 Haxe array.map(func)
list.map(func)
Lambda.map(iterable, func)J func list
list1 func list2
func/ list1, list2, list3 ,: list4
J의 배열 처리 기능은 map과 같은 연산을 암묵적으로 수행한다. 목록 길이가 같지 않으면 길이 오류 Java 8+ stream.map(func)
JavaScript 1.6
ECMAScript 5[https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map array#map(func)
]List1.map(function (elem1, i) {
return func(elem1, List2[i]); })List1.map(function (elem1, i) {
return func(elem1, List2[i], List3[i], ...); })Array#map은 func에 3개의 인수를 전달한다: 요소, 요소의 인덱스 및 배열. 사용하지 않는 인수는 생략할 수 있다. 필요에 따라 더 짧은 배열을 undefined 항목으로 확장하여 List1의 끝에서 중지 Julia map(func, list)
map(func, list1, list2)
map(func, list1, list2, ..., listN)
ERROR: DimensionMismatch Logtalk map(Closure, List)
map(Closure, List1, List2)
map(Closure, List1, List2, List3, ...) (최대 7개의 목록)
Closure 인자만 인스턴스화해야 함. 실패 Mathematica func /@ list
Map[func, list]MapThread[func, {list1, list2}]
MapThread[func, {list1, list2, ...}]
목록의 길이가 같아야 함 Maxima map(f, expr1, ..., exprn)
maplist(f, expr1, ..., exprn)map은 표현식의 선행 연산자와 동일한 연산자를 갖는 표현식을 반환함;
maplist는 목록을 반환함OCaml List.map func list
Array.map func arrayList.map2 func list1 list2
Invalid_argument 예외 발생 PARI/GP apply(func, list)
Perl map block list
map expr, listblock 또는 expr에서 특수 변수 $_는 목록의 각 값을 차례로 갖는다. 도우미 List::MoreUtils::each_array
는 가장 긴 항목이 소진될 때까지 여러 목록을 결합하고, 다른 항목은 undef
로 채운다.PHP array_map(callable, array)
array_map(callable, array1,array2)
array_map(callable, array1,array2, ...)
callable에 대한 인수의 수는
배열의 수와 일치해야 함.더 짧은 목록을 NULL 항목으로 확장 Prolog maplist(Cont, List1, List2).
maplist(Cont, List1, List2, List3).
maplist(Cont, List1, ...).
목록 인수는 입력, 출력 또는 둘 다임. zipWith, unzip, all도 포함 무음 실패 (오류 아님) Python map(func, list)
map(func, list1, list2)
map(func, list1, list2, ...)
Python 2에서는 목록을 반환하고, Python 3에서는 반복자를 반환한다. zip()
및 map()
(3.x)는 가장 짧은 목록이 끝나면 중지하고, map()
(2.x) 및 itertools.zip_longest()
(3.x)는 더 짧은 목록을 None
항목으로 확장한다.Ruby enum.collect {block}
enum.map {block}enum1.zip(enum2)
enum1.zip(enum2, ...)
[enum1, enum2, ...]enum은 열거형
호출된 객체(첫 번째 목록)의 끝에서 중지; 다른 목록이 더 짧으면 nil 항목으로 확장 Rust list1.into_iter().map(func)
list1.into_iter().zip(list2).map(func)
Iterator::map
및 Iterator::zip
메서드는 모두 원래 반복자의 소유권을 가져와 새 반복자를 반환한다. Iterator::zip
메서드는 내부적으로 list2
에 대해 IntoIterator::into_iter
메서드를 호출한다.가장 짧은 목록의 길이에서 중지 S-R lapply(list, func)
mapply(func, list1, list2)
mapply(func, list1, list2, ...)
더 짧은 목록은 순환됨 Scala list.map(func)
(list1, list2)
(list1, list2, list3)
참고: 3개 이상은 불가능. 가장 짧은 목록의 길이에서 중지 Scheme (Guile 및 Racket 포함) (map func list)
(map func list1 list2)
(map func list1 list2 ...)
목록은 모두 길이가 같아야 함 (SRFI-1은 길이가 다른 목록을 가져오도록 확장됨) Smalltalk aCollection collect: aBlock
aCollection1 with: aCollection2 collect: aBlock
실패 Standard ML map func list
ListPair.map func (list1, list2)
ListPair.mapEq func (list1, list2)2인자 map의 경우, func는 튜플로 인수를 받음 ListPair.map
은 가장 짧은 목록이 끝나면 중지하고, ListPair.mapEq
는 UnequalLengths
예외를 발생시킴Swift sequence.map(func)
zip(sequence1, sequence2).map(func)
가장 짧은 목록의 길이에서 중지 XPath 3
XQuery 3list ! block
for-each(list, func)
for-each-pair(list1, list2, func)
block
에서 컨텍스트 항목 .
은 현재 값을 갖는다.가장 짧은 목록의 길이에서 중지
3. 1. 함수형 언어
함수형 프로그래밍 언어에서 map 함수는 기본적으로 제공된다. Haskell, Lisp, Scheme, Clojure, Erlang, Elixir, F# 등 다양한 함수형 언어에서 찾아볼 수 있다.[3][4][5]
언어 | Map | 2개의 목록 Map | n개의 목록 Map | 비고 | 길이가 다른 목록 처리 |
---|---|---|---|---|---|
Common Lisp | (mapcar func list) | (mapcar func list1 list2) | (mapcar func list1 list2 ...) | 가장 짧은 목록의 길이에서 중지 | |
Clojure | (map func list) | (map func list1 list2) | (map func list1 list2 ...) | 가장 짧은 목록의 길이에서 중지 | |
Erlang | lists:map(Fun, List) | lists:zipwith(Fun, List1, List2) | zipwith3 도 사용 가능 | 목록은 길이가 같아야 함 | |
Elixir | Enum.map(list, fun) | Enum.zip(list1, list2) |> Enum.map(fun) | List.zip([list1, list2, ...]) |> Enum.map(fun) | 가장 짧은 목록의 길이에서 중지 | |
F# | List.map func list | List.map2 func list1 list2 | 다른 타입(Seq 및 Array)에 대한 함수가 존재함 | 예외 발생 | |
Haskell | map func list | zipWith func list1 list2 | zipWithn func list1 list2 ... | n 은 목록 수를 나타냄; zipWith7 까지 미리 정의됨 | 가장 짧은 목록의 길이에서 중지 |
Scheme (Guile 및 Racket 포함) | (map func list) | (map func list1 list2) | (map func list1 list2 ...) | 목록은 모두 길이가 같아야 함 (SRFI-1은 길이가 다른 목록을 가져오도록 확장됨) | |
Smalltalk | aCollection collect: aBlock | aCollection1 with: aCollection2 collect: aBlock | 실패 | ||
Standard ML | map func list | ListPair.map func (list1, list2) | 2인자 map의 경우, func는 튜플로 인수를 받음 | ListPair.map 은 가장 짧은 목록이 끝나면 중지하고, ListPair.mapEq 는 UnequalLengths 예외를 발생시킴 |
3. 1. 1. Haskell
Haskell에서 정수 목록 `[1, 2, 3, 4, 5]`의 각 정수를 제곱하려면, 먼저 단일 숫자를 제곱하는 함수를 정의한다.square x = x * x
그런 다음 `map` 함수를 호출한다.
>>> map square [1, 2, 3, 4, 5]
결과는 `[1, 4, 9, 16, 25]`이다. 이는 `map`이 목록 전체를 순회하며 각 요소에 `square` 함수를 적용했음을 보여준다.
Haskell에서 다형 함수 `map :: (a -> b) -> [a] -> [b]`는 `Functor` 타입 클래스에 속하는 모든 타입에 적용되는 다형적 함수 `fmap :: Functor f => (a -> b) -> f a -> f b`로 일반화된다.
리스트의 타입 생성자 `[]`는 `map` 함수를 사용하여 `Functor` 타입 클래스의 인스턴스로 정의할 수 있다.
instance Functor [] where
fmap = map
`Functor` 인스턴스의 다른 예로 트리가 있다.
- - 간단한 이진 트리
data Tree a = Leaf a | Fork (Tree a) (Tree a)
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Fork l r) = Fork (fmap f l) (fmap f r)
트리에 매핑을 적용하면 다음과 같은 결과가 나타난다.
>>> fmap square (Fork (Fork (Leaf 1) (Leaf 2)) (Fork (Leaf 3) (Leaf 4)))
Fork (Fork (Leaf 1) (Leaf 4)) (Fork (Leaf 9) (Leaf 16))
`Functor` 타입 클래스의 모든 인스턴스에 대해, `fmap`은 펀터 법칙을 준수해야 한다.
fmap id ≡ id -- 항등 법칙
fmap (f . g) ≡ fmap f . fmap g -- 합성 법칙
여기서 `.`는 Haskell의 함수 합성을 나타낸다.
이는 다양한 종류의 컬렉션에 대한 요소별 연산을 정의할 수 있게 해준다.
3. 1. 2. Lisp
Lisp는 1959년에 `maplist`라는 map 함수를 도입했으며,[3] 1958년에는 약간 다른 버전이 이미 등장했다.[4] 다음은 `maplist`의 원래 정의로, 연속적인 나머지 목록에 함수를 매핑한다.
maplist[x;f] = [null[x] -> NIL;T -> cons[f[x];maplist[cdr[x];f]]]
`maplist` 함수는 Common Lisp와 같은 최신 Lisp에서도 사용할 수 있지만,[5] `mapcar` 또는 더 일반적인 `map`과 같은 함수를 사용하는 것이 선호된다.
목록의 요소를 제곱하는 데 `maplist`를 사용하는 것은 S-표현식 표기법으로 다음과 같이 작성할 수 있다.
(maplist (lambda (l) (sqr (car l))) '(1 2 3 4 5))
`mapcar` 함수를 사용하면 위의 예제는 다음과 같이 표현할 수 있다.
(mapcar (function sqr) '(1 2 3 4 5))
3. 2. 절차적/객체지향 언어
C++의 표준 라이브러리에서는 `std::transform`을 사용하며, C# (3.0)의 LINQ 라이브러리에서는 `Select`라는 확장 메서드로 제공된다. Python, Ruby와 같은 고급 언어에서도 `map`이라는 이름으로 매핑 연산을 지원한다. Ruby에서는 `collect`라는 `map`의 별칭도 제공된다.[3]언어 | Map | 2개의 목록 Map | n개의 목록 Map | 비고 | 길이가 다른 목록 처리 |
---|---|---|---|---|---|
C++ | std::transform(begin, end, result, func) | std::transform(begin1, end1, begin2, result, func) | 헤더 begin, end, result는 반복자 result는 result에서 시작하여 쓰여짐 | ||
C# | ienum.Select(func) 또는 `select` 절 | ienum1.Zip(ienum2, func) | Select 는 확장 메서드ienum은 IEnumerable Zip 은 .NET 4.0에서 도입됨모든 .NET 언어에서도 유사하게 동작 | 가장 짧은 목록이 종료되면 중지 | |
Python | map(func, list) | map(func, list1, list2) | map(func, list1, list2, ...) | Python 2에서는 목록을 반환하고, Python 3에서는 반복자를 반환한다. | zip() 및 map() (Python 3.x)는 가장 짧은 목록이 끝나면 중지하고, map() (Python 2.x) 및 itertools.zip_longest() (Python 3.x)는 더 짧은 목록을 None 항목으로 확장한다. |
Ruby | enum.collect {block} | enum1.zip(enum2) | enum1.zip(enum2, ...) | enum은 열거형 | 호출된 객체(첫 번째 목록)의 끝에서 중지; 다른 목록이 더 짧으면 nil 항목으로 확장 |
3. 2. 1. C++
C++의 표준 라이브러리에서는 `std::transform`을 사용하여 매핑을 구현할 수 있다.[3]언어 | Map | 2개의 목록 Map | n개의 목록 Map | 비고 | 길이가 다른 목록 처리 |
---|---|---|---|---|---|
C++ | std::transform(begin, end, result, func) | std::transform(begin1, end1, begin2, result, func) | 헤더 begin, end, result는 반복자 result는 result에서 시작하여 쓰여짐 |
3. 2. 2. C#
C# (3.0)의 LINQ 라이브러리에서는 `Select`라는 확장 메서드로 매핑 기능을 제공한다.[3]Map | 2개의 목록 Map | 비고 | 길이가 다른 목록 처리 |
---|---|---|---|
ienum.Select(func) 또는 `select` 절 | ienum1.Zip(ienum2, func) | `Select`는 확장 메서드 ienum은 IEnumerable `Zip`은 .NET 4.0에서 도입됨 모든 .NET 언어에서도 유사하게 동작 | 가장 짧은 목록이 종료되면 중지 |
3. 2. 3. Python
파이썬에서 `map` 함수는 주어진 함수를 리스트, 튜플 등 반복 가능한 자료 구조의 각 요소에 적용한다.[3] Python 2에서는 `map` 함수가 목록을 반환했지만, Python 3에서는 반복자를 반환하도록 변경되었다.[3]기본적인 사용법은 다음과 같다.
```python
map(''func'', ''list'')
```
두 개 이상의 목록에 대해서도 `map` 함수를 적용할 수 있다.
```python
map(''func'', ''list1'', ''list2'')
```
이때, 각 목록에서 동일한 위치에 있는 요소들이 함수의 인자로 전달된다.
`map` 함수는 여러 개의 목록을 인자로 받을 수 있다.
```python
map(''func'', ''list1'', ''list2'', ...)
```
이 경우, `func`에 전달되는 인자의 수는 목록의 수와 일치해야 한다.[3]
길이가 다른 목록 처리`zip()` 및 `map()` (Python 3.x)은 가장 짧은 목록이 끝나면 중지한다. 반면 `map()` (Python 2.x) 및 `itertools.zip_longest()` (Python 3.x)는 더 짧은 목록을 `None` 항목으로 확장한다.[3]
3. 2. 4. Ruby
Ruby에서는 `map` 또는 `collect` 메서드를 사용하여 매핑을 수행할 수 있다. `map`의 별칭인 `collect`도 Ruby에서 제공된다 (Smalltalk에서 유래).[3]다음은 Ruby에서 `map` 메서드를 사용하는 예시이다.
```ruby
(1..5).map { |i| i * i } #=> [1, 4, 9, 16, 25]
```
2개 이상의 목록에 대해 `map`을 적용하려면, `zip` 메서드를 함께 사용할 수 있다.
```ruby
enum1.zip(enum2).map { |block| ... }
[enum1, enum2, ...].transpose.map { |block| ... }
```
`zip` 메서드는 호출된 객체(첫 번째 목록)의 끝에서 중지되며, 다른 목록이 더 짧으면 `nil` 항목으로 확장한다.[3]
3. 3. 기타 언어
Perl에서는 `map {''block''} ''list''` 또는 `map ''expr'', ''list''` 형태로 map 함수를 사용한다. 이때, `''block''`이나 `''expr''` 내에서 특수 변수 `$_`는 목록의 각 값을 차례로 가진다.[3] PHP에서는 `array_map(''callable'', ''array'')` 형태로 사용하며, `''callable''`에 전달되는 인수의 수는 배열의 수와 일치해야 한다. 더 짧은 목록은 `NULL` 항목으로 확장된다.[3]R에서는 `lapply(''list'', ''func'')` 형태로 사용하며, 2개 이상의 목록에 대해서는 `mapply(''func'', ''list1'', ''list2'', ...)`를 사용한다. 이때, 더 짧은 목록은 순환된다.[3]
Swift에서는 `''sequence''.map(''func'')` 형태로 사용하며, 2개의 목록에 대해서는 `zip(''sequence1'', ''sequence2'').map(''func'')`를 사용한다. 가장 짧은 목록의 길이에서 중지된다.[3]
Scala에서는 `''list''.map(''func'')` 형태로 사용하며, 2개의 목록에 대해서는 `(''list1'', ''list2'').zipped.map(''func'')`를 사용한다. 3개 이상의 목록에 대한 zipped.map은 불가능하며 가장 짧은 목록의 길이에서 중지된다.[3]
3. 4. 다중 목록 매핑
일부 프로그래밍 언어에서는 둘 이상의 목록에 대해 매핑을 수행하는 기능을 제공한다. 예를 들어, ''map2'' 또는 ''zipWith''와 같은 특수 이름을 사용하기도 한다. 2개 이상의 목록이 있는 경우, 목록의 길이가 다르면 처리 방식이 각 언어마다 다르다.[3]- 어떤 언어들은 예외를 발생시킨다.
- 어떤 언어들은 가장 짧은 목록의 길이를 기준으로, 해당 길이를 초과하면 중지하고 다른 목록의 추가 항목은 무시한다.
- 어떤 언어들은 가장 긴 목록의 길이를 기준으로, 이미 종료된 목록에 대해서는 값이 없음을 나타내는 특정 자리 표시자 값을 함수에 전달한다.
다음은 여러 프로그래밍 언어에서 제공하는 다중 목록 매핑 방식이다.
언어 | 2개의 목록 Map | n개의 목록 Map | 비고 | 길이가 다른 목록 처리 | |
---|---|---|---|---|---|
APL | list1 func list2 | func/ list1 list2 list3 list4 | APL의 배열 처리 기능은 map과 같은 연산을 암묵적으로 수행한다. | 목록 길이가 같지 않거나 1인 경우 길이 오류 | |
Common Lisp | (mapcar func list1 list2) | (mapcar func list1 list2 ...) | 가장 짧은 목록의 길이에서 중지 | ||
C++ | std::transform(begin1, end1, begin2, result, func) | 헤더 begin, end, result는 반복자 result는 result에서 시작하여 쓰여짐 | |||
C# | ienum1.Zip(ienum2, func) | Select 는 확장 메서드ienum은 IEnumerable Zip 은 .NET 4.0에서 도입됨모든 .NET 언어에서도 유사하게 동작 | 가장 짧은 목록이 종료되면 중지 | ||
Clojure | (map func list1 list2) | (map func list1 list2 ...) | 가장 짧은 목록의 길이에서 중지 | ||
D | zip(list1, list2).map!func | zip(list1, list2, ...).map!func | StoppingPolicy로 zip을 지정: shortest, longest, 또는 requireSameLength | ||
Erlang | lists:zipwith(Fun, List1, List2) | zipwith3 도 사용 가능 | 목록은 길이가 같아야 함 | ||
Elixir | Enum.zip(list1, list2) |> Enum.map(fun) | List.zip([list1, list2, ...]) |> Enum.map(fun) | 가장 짧은 목록의 길이에서 중지 | ||
F# | List.map2 func list1 list2 | 다른 타입(Seq 및 Array)에 대한 함수가 존재함 | 예외 발생 | ||
Groovy | [list1, list2].transpose().collect(func) | [list1, list2, ...].transpose().collect(func) | |||
Haskell | zipWith func list1 list2 | zipWithn func list1 list2 ... | n 은 목록 수를 나타냄; zipWith7 까지 미리 정의됨 | 가장 짧은 목록의 길이에서 중지 | |
J | list1 func list2 | func/ list1, list2, list3 ,: list4 | J의 배열 처리 기능은 map과 같은 연산을 암묵적으로 수행한다. | 목록 길이가 같지 않으면 길이 오류 | |
JavaScript 1.6 ECMAScript 5 | List1.map(function (elem1, i) { | List1.map(function (elem1, i) { | Array#map은 func에 3개의 인수를 전달한다: 요소, 요소의 인덱스 및 배열. 사용하지 않는 인수는 생략할 수 있다. | 필요에 따라 더 짧은 배열을 undefined 항목으로 확장하여 List1의 끝에서 중지 | |
Julia | map(func, list1, list2) | map(func, list1, list2, ..., listN) | ERROR: DimensionMismatch | ||
Logtalk | map(Closure, List1, List2) | map(Closure, List1, List2, List3, ...) (최대 7개의 목록) | Closure 인자만 인스턴스화해야 함. | 실패 | |
Mathematica | MapThread[func, {list1, list2}] | MapThread[func, {list1, list2, ...}] | 목록의 길이가 같아야 함 | ||
OCaml | List.map2 func list1 list2 | Invalid_argument 예외 발생 | |||
Prolog | maplist(Cont, List1, List2). | maplist(Cont, List1, List2, List3). | maplist(Cont, List1, ...). | 목록 인수는 입력, 출력 또는 둘 다임. zipWith, unzip, all도 포함 | 무음 실패 (오류 아님) |
Python | map(func, list1, list2) | map(func, list1, list2, ...) | Python 2에서는 목록을 반환하고, Python 3에서는 반복자를 반환한다. | zip() 및 map() (3.x)는 가장 짧은 목록이 끝나면 중지하고, map() (2.x) 및 itertools.zip_longest() (3.x)는 더 짧은 목록을 None 항목으로 확장한다. | |
Ruby | enum1.zip(enum2).map {block} | enum1.zip(enum2, ...).map {block} | enum은 열거형 | 호출된 객체(첫 번째 목록)의 끝에서 중지; 다른 목록이 더 짧으면 nil 항목으로 확장 | |
Rust | list1.into_iter().zip(list2).map(func) | Iterator::map 및 Iterator::zip 메서드는 모두 원래 반복자의 소유권을 가져와 새 반복자를 반환한다. Iterator::zip 메서드는 내부적으로 list2 에 대해 IntoIterator::into_iter 메서드를 호출한다. | 가장 짧은 목록의 길이에서 중지 | ||
S-R | mapply(func, list1, list2) | mapply(func, list1, list2, ...) | 더 짧은 목록은 순환됨 | ||
Scala | (list1, list2).zipped.map(func) | (list1, list2, list3).zipped.map(func) | 참고: 3개 이상은 불가능. | 가장 짧은 목록의 길이에서 중지 | |
Scheme (Guile 및 Racket 포함) | (map func list1 list2) | (map func list1 list2 ...) | 목록은 모두 길이가 같아야 함 (SRFI-1은 길이가 다른 목록을 가져오도록 확장됨) | ||
Smalltalk | aCollection1 with: aCollection2 collect: aBlock | 실패 | |||
Standard ML | ListPair.map func (list1, list2) | 2인자 map의 경우, func는 튜플로 인수를 받음 | ListPair.map 은 가장 짧은 목록이 끝나면 중지하고, ListPair.mapEq 는 UnequalLengths 예외를 발생시킴 | ||
Swift | zip(sequence1, sequence2).map(func) | 가장 짧은 목록의 길이에서 중지 | |||
XPath 3 XQuery 3 | for-each-pair(list1, list2, func) | block 에서 컨텍스트 항목 . 은 현재 값을 갖는다. | 가장 짧은 목록의 길이에서 중지 |
4. 최적화
맵의 수학적 기반은 여러 가지 최적화를 가능하게 한다.[2]
위의 단일 연결 리스트에 대한 맵 구현은 꼬리 재귀가 아니므로, 큰 리스트로 호출될 때 스택에 많은 프레임을 쌓을 수 있다. 많은 언어는 대안으로 "역 맵" 함수를 제공하는데, 이는 매핑된 리스트를 뒤집는 것과 동일하지만 꼬리 재귀이다. 다음은 접기-왼쪽 함수를 사용하는 구현이다.
reverseMap f = foldl (\ys x -> f x : ys) []
단일 연결 리스트를 뒤집는 것 또한 꼬리 재귀이므로, 역 맵과 뒤집기를 함께 사용하여 꼬리 재귀 방식으로 일반 맵을 수행할 수 있지만, 리스트를 두 번 반복해야 한다.
4. 1. 맵 퓨전 (Map Fusion)
합성 법칙은 다음 두 가지가 동일한 결과를 낸다는 것을 보장한다.- `(map f . map g) list`
- `map (f . g) list`
즉, 이다. 하지만, 두 번째 형태가 첫 번째 형태보다 계산 효율성이 더 높다. 왜냐하면 각 `map`은 처음부터 전체 리스트를 다시 만들어야 하기 때문이다. 따라서, 컴파일러는 첫 번째 형태를 두 번째 형태로 변환하려고 시도한다. 이러한 유형의 최적화를 ''맵 퓨전''이라고 하며, 이는 함수형 프로그래밍의 루프 퓨전과 유사하다.[2]
4. 2. 맵-폴드 퓨전 (Map-Fold Fusion)
`map` 함수는 접기 (예: `foldr`)를 사용하여 정의할 수 있으며, 종종 그렇게 정의된다. 이는 `foldr f z . map g`가 `foldr (f . g) z`와 동일한 ''map-fold 퓨전''을 수행할 수 있음을 의미한다.[2]5. 일반화
Haskell에서, 다형 함수 `map :: (a -> b) -> [a] -> [b]`는 다형적 함수 `fmap :: Functor f => (a -> b) -> f a -> f b`로 일반화되는데, 이는 `Functor` 타입 클래스에 속하는 모든 타입에 적용된다.[1]
리스트의 타입 생성자 `[]`는 `map` 함수를 사용하여 `Functor` 타입 클래스의 인스턴스로 정의할 수 있다.
instance Functor [] where
fmap = map
`Functor` 인스턴스의 다른 예로는 아래와 같은 간단한 이진 트리가 있다.
data Tree a = Leaf a | Fork (Tree a) (Tree a)
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Fork l r) = Fork (fmap f l) (fmap f r)
트리에 맵핑하면 다음과 같은 결과가 생성된다.
>>> fmap square (Fork (Fork (Leaf 1) (Leaf 2)) (Fork (Leaf 3) (Leaf 4)))
Fork (Fork (Leaf 1) (Leaf 4)) (Fork (Leaf 9) (Leaf 16))
`Functor` 타입 클래스의 모든 인스턴스에 대해, `fmap`은 다음의 펀터 법칙을 준수해야 한다.
fmap id ≡ id -- 항등 법칙
fmap (f . g) ≡ fmap f . fmap g -- 합성 법칙
여기서 `.`는 Haskell의 함수 합성을 나타낸다.
이는 다양한 종류의 컬렉션에 대한 요소별 연산을 정의할 수 있게 해준다.
5. 1. 범주론적 배경
범주론에서 함자(Functor)는 두 범주 사이의 사상(morphism)을 보존하는 구조이다. `Functor` 타입 클래스는 이러한 함자의 개념을 프로그래밍 언어에 도입한 것이다. 함자 법칙을 준수함으로써 요소별 연산을 다양한 종류의 컬렉션에 대해 정의할 수 있다.Haskell에서, 다형 함수 `map :: (a -> b) -> [a] -> [b]`는 다형적 함수 `fmap :: Functor f => (a -> b) -> f a -> f b`로 일반화되는데, 이는 `Functor` 타입 클래스에 속하는 모든 타입에 적용된다.
데이터 유형의 우주를 함수를 사상으로 하는 범주 으로 해석하면, `Functor` 타입 클래스의 멤버인 타입 생성자 `F`는 함자의 객체 부분이고, `fmap :: (a -> b) -> F a -> F b`는 사상 부분이다.
함자 법칙은 다음과 같다.
- `fmap id ≡ id` (항등 법칙)
- `fmap (f . g) ≡ fmap f . fmap g` (합성 법칙)
여기서 `.`는 함수 합성을 나타낸다.
자연 변환(Natural Transformation)은 두 함자 사이의 변환을 나타내는 개념이다. 두 함자 가 주어지면, 자연 변환 는 범주 의 각 객체 에 대해 하나의 사상 의 모음으로 구성된다.[1] 자연 변환은 `eta :: F a -> G a` 형식의 함수에 해당하며, 여기서 `a`는 보편적으로 정량화된 타입 변수이다.
6. 활용 예시
map 함수는 리스트의 각 원소에 특정 함수를 적용하는 데 사용된다. 예를 들어, `[1, 2, 3, 4, 5]`와 같은 정수 목록이 있을 때, 각 숫자를 제곱하는 경우를 생각해 볼 수 있다. 하스켈(Haskell)에서는 먼저 숫자를 제곱하는 함수를 정의한다.
```haskell
square x = x * x
```
그런 다음 `map` 함수를 사용하여 다음과 같이 각 숫자에 `square` 함수를 적용할 수 있다.
```haskell
>>> map square [1, 2, 3, 4, 5]
```
결과는 `[1, 4, 9, 16, 25]`가 된다. 이는 `map` 함수가 목록의 각 원소를 순회하며 `square` 함수를 적용했음을 보여준다.
정수 목록에 대한 매핑의 시각적 예시는 시각적 예시 섹션에서 확인할 수 있다.
6. 1. 시각적 예시
정수 목록 `X = [0, 5, 8, 3, 2, 1]`에 대해 함수 에 따라 새로운 목록 `X'`으로 매핑하는 각 단계를 시각적으로 표현하였다.
참조
[1]
간행물
Theorems for free!
https://people.mpi-s[...]
Association for Computing Machinery
1989-09
[2]
웹사이트
Map fusion: Making Haskell 225% faster
http://www.randomhac[...]
[3]
문서
LISP Programmer's Manual
http://www.softwarep[...]
1959-03
[4]
문서
Symbol Manipulating Language - Revisions of the Language
http://www.softwarep[...]
AI Memo No. 4
1958-10
[5]
웹사이트
Function MAPC, MAPCAR, MAPCAN, MAPL, MAPLIST, MAPCON in ANSI Common Lisp
http://www.lispworks[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com