본문 바로가기
iOS/Swift

[iOS/Swift] 유용한 대표 고차함수, map, filter, reduce 다루기

by iosdevlime 2023. 4. 5.

요 근래 코딩테스트를 슬쩍 건드려 보구 있는데 말입니다😨

 

배열 관련 문제를 풀때, 고차함수를 활용하는게 여간 손에 익질 않아

길고 긴 하드 코딩으로 꾸역꾸역 통과하는 경우가 많았는데요..

 

그래서, 이번 포스팅에서는 배열을 다룰때 자주 활용되는 

map, filter, reduce 예시 코드를 통해 정리해보는 시간을 가져볼까 합니다.

 

 


 

 

근데, 고차함수가 무엇인가요?

다른 함수를 ①매개변수로 받거나, ②함수의 결과를 반환하는 함수

 

매개변수로 받고, 함수의 결과로 받는다라..

이전에 다뤘던 클로저의 1급 객체함수의 특징과 비슷하지 않나요?

 

고차함수란 Swift에서 이미 제공되고 있는 표준 라이브러리로서,

함수형 패러다임을 지원하는 Swift에서는 다양한 고차함수를 사용할 수 있습니다.

 

주로 사용하는 map, filter, reduce 함수는

컨테이너 타입 (Array, Set, Dictionary)등에 구현이 되어 있기 때문에

 

흔히 '하드코딩'을 하지 않고서도 충분히 새로운 컨테이너, 기능을 사용할 수 있습니다.

 

 


 

 

map

기존의 컨테이너 요소를 매개변수로 받아, 변환한 값을 새로운 컨테이너에 담아 반환

 

map은 특정 컨테이너의 데이터를 변형하기 위해 활용하는 고차함수입니다.

새로운 컨테이너를 만들기 때문에, 기존 데이터는 변형되지 않습니다.

 

해당 고차함수는, 기존의 조건문인 for-in 구문과 유사하나,

코드가 매우 간결해지고, 재사용이 용이하다는 장점이 있습니다.

 

 

고차함수 map의 선언

  • 고차함수 map의 기본 형태는 다음과 같습니다.
    • 매개변수 transform은, 기존 컨테이너의 요소를 매개변수로 받습니다.
    • 이후, rethrows를 통해 변환된 클로저를 새로운 컨테이너에 담아 반환합니다.
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

 

 

예시 : 문자열(String)배열을 정수(Int)배열로 변환

  • for-in 구문을 활용한다면, 다음과 같은 코드를 작성할 수 있습니다.
    • 기존 컨테이너(배열) stringNumber는 무작위의 정수 배열값을 가지고 있습니다.
    • if let을 통해 옵셔널 바인딩을 실행한 후, 빈 정수 배열인 intNumber에 값을 할당합니다.
let stringNumber: [String] = ["1","3","5","7"] // 문자열 타입의 배열
var intNumber: [Int] = Array<Int>() // 정수타입의 새로운 빈 배열

for number in stringNumber {
    // 옵셔널 바인딩(if let)을 통해, 조건값을 정수로 변환
    if let transformNumberToInt = Int(number) {
        intNumber.append(transformNumberToInt) // 빈 배열에 조건값을 할당함 
    }
}

print(intNumber) // [1, 3, 5, 7]

 

  • 고차함수 map을 활용하면, 다음과 같이 코드를 작성할 수 있습니다.
    • 기존 컨테이너(stringNumber)가 문자열 타입이므로, 이를 매개변수로 받습니다.
    • 반환 타입으로 정수(Int) 타입을 설정한 후, 반환값으로는 매개변수 문자열 ➟ 정수로 변환합니다.
let stringNumber: [String] = ["1","3","5","7"]

// 매개변수는 문자열(stringNumber)이며, 이를 정수(Int)형으로 변환합니다.
var intNumber: [Int] = stringNumber.map { (number: String) -> Int in
    return Int(number) ?? 0 // 반환하는 타입(intNumber 배열에 할당되는 값)은 Int로 타입변환된 값입니다.
}

print(intNumber) // [1, 3, 5, 7]

 

  • 후행 클로저(trailing Clousre)를 통해, 보다 간결하게 코드를 작성할 수 있습니다.
    • 아래 코드는 매개변수, 반환타입 및 반환 키워드를 축약한 형태입니다.
let stringNumber: [String] = ["1","3","5","7"]
var intNumber: [Int] = stringNumber.map { (Int($0)) ?? 0 }

print(intNumber) // [1, 3, 5, 7]

 

 


 

 

filter

기존의 컨테이너의 전체 요소를 매개변수로 받아, 필터링 한 값을 새로운 컨테이너에 담아 반환

 

filter은 특정 컨테이너의 요소별 패턴을 파악, 조건을 통해 반환하는 고차함수입니다.



컨테이너 별 요소의 형태를 변환하여 컨테이너를 반환하는 고차함수 map과는 달리,

조건을 통해 기존 컨테이너를 재 활용한다는 점에서 활용방안에 대한 차이점을 확인할 수 있습니다.

 

 

 

고차함수 filter의 선언

  • 고차함수 filter의 기본 형태는 다음과 같습니다.
    • 매개변수 islncluded는, 기존 컨테이너의 요소를 매개변수로 받습니다.
    • 특정 조건에 따라 배열에 포함되어야 하는지 여부를 확인, Bool 타입으로 반환합니다.
      (즉, isIncluded에 따라, true값에 만족하는 요소만이 새로운 컨테이너에 포함됩니다)
func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

 

 

예시 : 기존 배열에서, 짝수 요소만을 반환하는 배열

  • for-in 구문을 활용한다면, 다음과 같은 코드를 작성할 수 있습니다.
    • 기존 컨테이너 input은 무작위의 정수값을 요소로 가지고 있습니다.
    • for-in 구문을 통해, 나머지가 0인 요소만을 새로운 컨테이너 output에 할당합니다.
let input: [Int] = [1,4,7,8,12,15]
var output: [Int] = Array<Int>()

for number in input {
    // 배열의 요소를 2로 나누었을 경우, 나머지가 0일 경우에만 새로운 컨테이너 output에 담아냄
    if number % 2 == 0 {
        output.append(number)
    }
}

print(output) // [4, 8, 12]

 

  • 고차함수 filter을 활용하면, 다음과 같이 코드를 작성할 수 있습니다.
    • 새로운 컨테이너인 output기존 input 배열의 filter를 통해 요소를 가져옵니다.
    • 반환타입은 Boolean으로, 내부 실행 Scope에서는 조건문을 작성합니다.
let input: [Int] = [1,4,7,8,12,15]
var output: [Int] = input.filter { (number: Int) -> Bool in
    return number % 2 == 0 // 조건문 작성 (Boolean)
}

print(output) // [4, 8, 12]

 

  • 후행 클로저(trailing Clousre)를 통해, 보다 간결하게 코드를 작성할 수 있습니다.
    • 아래 코드는 매개변수, 반환타입 및 반환 키워드를 축약한 형태입니다.
let input: [Int] = [1,4,7,8,12,15]
var output: [Int] = input.filter { $0 % 2 == 0 }

print(output) // [4, 8, 12]

 

 


 

 

reduce

컨테이너 내부의 요소를 매개변수로 받아, 하나로 통합하여 반환

 

reduce은 앞서 map, filter와는 달리 '결합'이라는 특징을 가진 고차함수입니다.

 

 

①결합을 위한 새로운 초기값을 설정할 수 있으며, 

②기존 컨테이너의 요소의 합을 매개변수로 설정, 

 

과 간의 관계를 설정하여 반환합니다.

(예시를 통해 쉽게 이해할 수 있습니다)

 

 

 

고차함수 reduce의 선언

  • 고차함수 reduce의 기본 형태는 다음과 같습니다.
    • map과 동일하게 기존 컨테이너의 요소<새로운 타입/Result>으로 변환할 수 있습니다.
    • ①initialResult 매개변수를 통해 초기값을 설정할 수 있으며,
    • ②nextPartialResult 매개변수(기존 컨테이너 요소)를 통해 ①, 간의 관계를 설정, 반환합니다.
func reduce<Result>(_ initialResult: Result,
                    _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

 

 

 

예시 : 기존 정수(Int) 배열의 각 요소를 모두 더한 값을 반환

  • for-in 구문을 활용한다면, 다음과 같은 코드를 작성할 수 있습니다.
    • 기존 numbers 컨테이너의 요소를 반복문을 통해 sum 변수값에 할당합니다.
    • sum 변수는 1, 3, 5, 7, 9를 모두 더한 누적값인 25가 할당됩니다.
let numbers: [Int] = [1,3,5,7,9]
var sum: Int = 0

for number in numbers {
    sum += number
}

print(sum) // 25 (1+3+5+7+9)

 

 

  • 고차함수 reduce을 활용하면, 다음과 같이 코드를 작성할 수 있습니다.
    • 초기값(initialResult)은 0으로 설정되는 동시에 first 매개변수에 할당됩니다.
    • second 매개변수는 기존 컨테이너인 numbers의 ②요소값(nextPartialResult)입니다.
    • 반환값으로는 초기값(initialResult) + ②요소 누적값(nextPartialResult)이 선언됩니다.
let numbers: [Int] = [1,3,5,7,9]

// reduce(괄호 -> initialResult) { (클로저 내부의 첫번째 매개변수는 initialResult, 두번째 매개변수는 요소값) -> 누적값을 반환 }
var sum: Int = numbers.reduce(0) { (first: Int, second: Int) -> Int in
    return first + second // first(initialResult) + second(요소의 누적(덧셈)값)
}

print(sum) // 25

 

 

  • 후행 클로저(trailing Clousre)를 통해, 보다 간결하게 코드를 작성할 수 있습니다.
    • 아래 코드는 매개변수, 반환타입 및 반환 키워드를 축약한 형태입니다.
let numbers: [Int] = [1,3,5,7,9]
var sum: Int = numbers.reduce(0){ $0 + $1 }

print(sum) // 25

 


 

위와 같은 3가지 대표 고차함수 이외,

compactMap, flatMap 등 2차원 배열에서 활용되는 등 다양한 함수가 존재합니다.

 

해당 내용은 Combine외 타 포스팅에서 슬쩍 함께 다뤄보도록 하겠습니다.

댓글