2014년 7월 24일 목요일

Swift - Collection 타입의 도구들: map, filter, reduce, zip

기초적이지만 알아두면 나쁠 건 없는 스위프트(Swift)의 Collection Type (주로 Array) 활용 메소드 map, filter 그리고 reduce를 간단히 복습해보는 글. 추가로 zip에 관해서도 정리해 보자.

이미 애플의 스위프트 공식 책(?)에도 간단한 예제가 나와있기 때문에 이해했다면 굳이 볼 필요는 없는 내용이다.

map

map 이라고 해서 '지도' 라는 의미는 아니다. 오히려 이름 그대로 '매핑(Mapping)' 이라는 단어가 더 와닿을지도 모르겠다.

이 메소드는 특정 Array나 Dictionary를 기준으로 다른 Array를 만들어 낼 때 사용하는 메소드다. Array에서 이용하는 경우 map의 원형은 아래와 같다.
Array.map(transform: T -> U)
위의 원형이라고 적은것에서 두 가지 타입 T와 U표현을 잘 보자. 타입이 유동적이다. transform 으로 넘기는 것은 클로져(Closure)인데 T는 원래 Array의 타입이고 U는 새롭게 만들 Array의 아이템 타입이다.
let values = [1, 2, 3, 4, 5]

let valueStrings = values.map({ (v: Int) -> (String) in
    return "\(v)"
})

println(valueStrings)
// 콘솔에 ["1", "2", "3", "4", "5"] 가 출력된다.
정수형 아이템들을 가지는 배열(Array) values를 만들었다. 그리고 이 values를 map을 이용해 정수형이 아닌 문자열형(String) Array를 만들어냈다.

단순하게 생각해보면, map은 배열을 검토해서 정리할 때 활용된다고 볼 수 있다.

추가로, Dictionary 에서 map을 사용할 때는 아래와 같은 식이다.
Dictionary.map(transform: (KEY, VALUE) -> U)
인자가 키와 값 두 가지로 나눠져서 넘어간다는 것을 제외하면 거의 동일하다.
let dict: [String: Int] = [ "a": 1, "b": 2 ]
dict.map { (key, value) in
  return "\(key)=\(value)"
}
앞서 본 예제가 이해되었다면 이 예제도 이해가 될 것이다.

filter

필터는 딱 와닿는 이름이다. 배열(Array)에서 특정 아이템을 걸러낼 때 이용한다.
Array.filter(includeElement: T -> Bool)
역시나 배열의 아이템 타입에 맞게 클로져를 넘겨주어야 한다. 그런데 이번에는 Bool 타입을 리턴하는 클로져다. 대충 상상해보면 아마도 false를 리턴하는 아이템은 걸러내는 형식인 것 같다.
let evenValues = values.filter({ (v: Int) -> (Bool) in
    if v % 2 == 0 { return true }
    return false
})

println(evenValues)
// 콘솔에 [2, 4] 가 출력된다.
예상이 맞다. filter는 배열에서 필요없는 아이템을 걸러낸 배열을 리턴하는 메소드다.

Dictionary를 필터링 하는 경우도 비슷하긴 한데 뭔가 미묘하게 다르다.
let dict: [String: Int] = [ "a": 1, "b": 2 ]
dict.filter { (key, value) in
  return (value == 2)
}
이 경우 value가 2인 두 번째 아이템이 넘어와야 할 것 같은데, (String, Int) 형식의 튜플 형식의 Array로 넘어온다. 이런 경우에는 주의해서 사용하자.

reduce

reduce의 경우는 좀 다르다. 이 녀석은 combine 이라는 표현이 더 잘 어울리는데, 하는 일은 배열의 모든 아이템을 꺼내서 하나로 합치는 형태이기 때문이다.
Array.reduce(initial: U, combine: (U, T) -> U)
initial 이라는 것은 초기값이다. 즉 initial 부터 시작해서 combine 클로져를 통해 연산을 한다.
let sumValues = values.reduce(0, combine: { (v1: Int, v2: Int) -> (Int) in
    return v1 + v2
})

println(sumValues)
// 콘솔에 15 가 출력된다.
위 코드에서 reduce의 동작 방식을 유추해보자.
  1. initial 값인 0 부터 시작
  2. 0과 value[0] 아이템을 꺼내서 연산
  3. 앞에서 연산된 결과와 values[1] 아이템을 꺼내서 연산
  4. 앞에서 연산된 결과와 values[2] 아이템을 꺼내서 연산
  5. ...
  6. 앞에서 연산된 결과와 values의 마지막 아이템을 꺼내서 연산
이런 순으로 동작한다.

zip

zip은 두 리스트를 짝지어서 합쳐준다.
let names = [ "Kim", "Park", "Lee" ]
let scores = [ 90, 95, 80 ]
let scoreBoard = zip(names, scores)
// Zip2Sequence<Array<String>, Array<Int>>
결과물은 Zip2Sequence 타입이라는 특이한 형식이 넘어오는데, 이 녀석은 for 루프로 이터레이션 할 때 유용하다.
for (name, score) in scoreBoard {
  print("\(name) = \(score)")
}
결과물을 예상해보자.

AnyObject와의 연동

싫은 좋든 간에, 스위프트로 코딩을 하더라도 Objective-C로 구현된 NSArray를 함께 사용하게 될 가능성이 높다. 특히 UIKit이나 Cocoa를 이용한다면 말이다. 그렇다면 Array의 타입도 AnyObject 형식으로 자주 사용하게 된다.

이 AnyObject의 경우 'is' 라는 오퍼레이터를 이용해 타입을 확인할 수가 있다. 따라서 아래 예제와 같이 이용이 가능하다.
let objectList: Array = [10, "이십", 100, "이백"]

// 정수만 걸러내기 ------------------------------
let objectIntList = objectList.filter({ (input: AnyObject) -> (Bool) in
    return input is Int
})

println(objectIntList)
// 콘솔에 [10, 100] 이 출력된다.



// 문자열만 걸러내기 -----------------------------
let objectStringOnlyList = objectList.filter({ (input: AnyObject) -> (Bool) in
    return input is String
})

println(objectStringOnlyList)
// 콘솔에 ["이십", "이백"] 이 출력된다.



// 문자열 배열 구하기 ----------------------------
let objectStringList = objectList.map({ (input: AnyObject) -> (String) in
    if input is String {
        return input as String
    } else {
        return "\(input)"
    }
})

println(objectStringList)
// 콘솔에 ["10", "이십", "100", "이백"] 이 출력된다.
AnyObject 타입의 아이템을 가지는 Array를 map과 filter로 활용하는 예제이다. 앞서 이야기 한 대로 'is' 라는 커맨드를 활용하고 있다. (사실 마지막 예제는 그냥 몽땅 마지막 리턴문으로 처리해 버리면 되는데 그냥 예제상 이렇게 했다. 오해하지 말자 -_-)

그런데  reduce의 경우는 좀 애매하다. 애초에 목적이 배열의 데이터를 이용해 연산하는 것이다 보니 AnyObject로 활용하기에는 좀 귀찮다. 초기값과 combine 클로져의 리턴값의 타입을 맞춰야 한다는 것을 생각해보자. 하기싫어진다. :-)

이상 Array의 세 가지 도구를 살펴봤다.

참고로 Python이나 Lisp 같은 언어를 이용해 봤다면 메소드 이름 만으로도 뭘 하는 것인지는 파악이 가능하리라 생각된다.

flatMap

Swift 2.0에 추가된 map과 비슷한 녀석이지만 다른 녀석이다. 아래 글을 참고하자.
[관련글] Swift - flatMap 넌 도데체 뭐냐​

[관련글] 스위프트(Swift) 가이드

댓글 없음 :