Old_SWIFT(221012)/함수이야기

<Map> 함수 더하기 추론<조금>, <compactMap>, <flatMap>

KataRN 2021. 11. 12. 14:41
반응형

안녕하세요. KataRN입니다.

 

오늘은 함수 Map에 대해 정리해보려고합니다.

 

제가 프로그래머스 1단계를 쭉 풀면서 다른사람들이 푼 내용과 비교도 해봤는데 map, reduce, filter 이 3가지를 쓴 풀이들이 코드가 깔끔합니다. 그리고 추론을 하면 할 수록 코드는 더 간결해지죠.(단 상대적으로 효율성은 떨어지더라구요...)

문제는 코드가 간결한 만큼 읽는 순간 이해가 빠르게 되느냐인데 제가 추론에 약해서인지 처음엔 힘들었습니다.

그래서 이번 기회에 3가지 함수에 대해 정리를 하고 응용도 해보겠습니다.

 

본문과관련이있는듯없는듯_윌리를찾아보세요.gif

우선 공식문서를 통해 알아보도록 하죠.

map(_:)

Returns an array containing the results of mapping the given closure over the sequence’s elements.
 

Declaration

func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]

Parameters

transform

A mapping closure. transform accepts an element of this sequence as its parameter and returns a transformed value of the same or of a different type.

Return Value

An array containing the transformed elements of this sequence.

Discussion

In this example, map is used first to convert the names in the array to lowercase strings and then to count their characters.

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
//1번예제
let lowercaseNames = cast.map { $0.lowercased() }
// 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
//2번예제
let letterCounts = cast.map { $0.count }
// 'letterCounts' == [6, 6, 3, 4]

우선 애플문서에 있는건 func map<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T] 이런 형태인데...

왜 예제는 map{$0} 이런 형태이냐?는 의문이 들겠죠...

저도 압니다 그기분... 구체적으로 어떤 과정을 거쳐서 어떻게 코드가 변했는지 알려드릴게요.

애플 문서를 기준으로 보여드리겠습니다.

let cast = ["Vivien", "Marlon", "Kim", "Karl"]

let lowercaseNames = cast.map( { (value: String) -> String in return value.lowercased() } )
//기본배열

let lowercaseNames = cast.map( { (value) -> String in value.lowercased() } )
//1단계축소

let lowercaseNames = cast.map( {value in value * 2 } )
//2단계축소

array.map( {$0 * 2} )
//3단계축소

array.map{$0 * 2}
//4단계축소

놀랍게도 기본배열부터 4단계축소된 배열까지 모두 같은 값입니다...

Swift는 추론을 통해 이렇게 축약할 수 있습니다.

이어서 설명하겠습니다.

 

Array.map

1번 예제를 보면 map을통해 배열안의 값들을 변경할 수 있군요.

map 안에 있는 $0은 cast의 값을 의미합니다. "cast배열로 다시 배열을 만들건데 안의 값들을 lowercased(소문자로변경)해줘" 라는거죠

2번예제를 보면 마찬가지로 "각각의값들의 자리수로 배열을 만들어줘."라는겁니다.

 

String.map

이번엔 String에 map을 사용하면 어떻게 될까요?

let str = "123456"
print(str.map{$0})
//["1", "2", "3", "4", "5", "6"]

 

하지만 여기서 주의해야될 점이 있습니다.

string를 map을 이용하여 배열로 나눈다면 값이 String가 아닌 Character타입으로 만들어집니다.

이것의 차이는 다음에 정리해서 다시 글을 올리도록하겠습니다.

 

"그렇다면 굳이 지금 말하는 이유가 뭐냐?"라고 질문하시는 분들을 위해 예제 하나 더 준비하겠습니다.

위의 예제에서 나온 배열은 지금 [String]입니다. 이걸 [Int]로 바꿔보시면 압니다.

let str = "123456"
print(str.map{Int($0)})

이렇게 하면 될거같지만 안됩니다...

Character -> Int로 못바꾸기 때문입니다.

겉보기엔 String -> Int 처럼 생겼지만 아닙니다.

 

그러므로 아래처럼 하셔야 변경이 가능합니다.(주의하셔야합니다.)

let str = "123456"
print(str.map{Int(String($0))!})
//[1, 2, 3, 4, 5, 6]

 

그리고 여기서 끝이 아닙니다...

Map 함수는 flatMap, compactMap으로 나뉩니다...

 

저는 swift5부터 시작해서 몰랐지만 아래와같습니다.

<Swift 4.1부터는 flatMap이 compactMap로 변경되지만, flatMap이 없어지는 것이 아닙니다.>

 

flatMap와 compactMap을 동시에 보시죠

func flatMap<SegmentOfResult>(_ transform: ([Int]) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
func compactMap<ElementOfResult>(_ transform: (Int?) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

같은듯 다릅니다.

이건 쓰임새를 정확하게 나누겠다는 뜻입니다.

머리아프니 예제를 통해 비교하겠습니다.

let array = [1, nil, 3, nil, 5, nil, 7]

let mapTest = array.map{ $0 }
let flatMapTest = array.flatMap{ $0 }
let compactMapTest = array.compactMap { $0 }

print("mapTest :", mapTest)
print("flatMapTest :", flatMapTest)
print("compactMapTest :", compactMapTest)

//mapTest : [Optional(1), nil, Optional(3), nil, Optional(5), nil, Optional(7)]
//flatMapTest : [1, 3, 5, 7]
//compactMapTest : [1, 3, 5, 7]

결과를 보니 map의 경우 optional과 nil을 포함하지만

flatMap과 compactMap는 제거되네요.

그럼 둘의 차이는 뭘까요ㅎㅎ

 

1차원 배열이라 그렇습니다.

2차원 배열에서는 또 다른 결과가 나옵니다.

역시 예제로 확인해보겠습니다.

let array: [[Int?]] = [[1, nil, 3], [nil, 5], [nil, 6], [nil, nil]]

let mapTest = array.map{ $0 }
let flatMapTest = array.flatMap { $0 }
let compactMapTest = array.compactMap { $0 }

print("mapTest :", mapTest)
print("flatMapTest :",flatMapTest)
print("compactMapTest :",compactMapTest)

//mapTest : [[Optional(1), nil, Optional(3)], [nil, Optional(5)], [nil, Optional(6)], [nil, nil]]
//flatMapTest : [Optional(1), nil, Optional(3), nil, Optional(5), nil, Optional(6), nil, nil]
//compactMapTest : [[Optional(1), nil, Optional(3)], [nil, Optional(5)], [nil, Optional(6)], [nil, nil]]

우선 결과만 봤을때 2차원 배열의 경우 nil과 optional이 제거되지 않네요.

map과 compactMap의 결과물이 같으므로 compactMap와 flatMap만 비교해보겠습니다.

flatMap의 경우 2차원 배열을 1차원배열로 모두 데려왔네요.

2차원배열 -> 1차원배열로 만들때는 flatMap을 쓰면 됩니다.

그런데말입니다... optional과 nil이 있는걸 보니 지저분하네요. 이걸 다시 이쁘게 하려면?

let array: [[Int?]] = [[1, nil, 3], [nil, 5], [nil, 6], [nil, nil]]
let prettyArr = array.flatMap { $0 }.compactMap{ $0 }
print("prettyArr :",prettyArr)

//prettyArr : [1, 3, 5, 6]

 

이뻐졌군요😊

 

저는 제글을 읽으신 분들이 바로바로 응용이 가능하게 되는것을 목표로 글을 작성하고 있는데 목표달성이 되고있는지 모르겠네요😭

더욱 노력하겠습니다.

 

 

오늘도 읽어주셔서 감사합니다.

반응형