→ 함수를 다른 함수의 전달인자로 사용할 수 있음
출처: 스위프트 프로그래밍(3판) 3부 15장 by 야곰
→ 그래서 맵은 기존데이터를 transform(변형)하는 데 많이 사용함
Q. map을 왜 쓰는가?
for 구문 사용
import Foundation
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int] = [Int]()
var strings: [String] = [String]()
for number in numbers {
doubledNumbers.append(number * 2)
strings.append("\(number)")
}
print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]
map 메서드 사용
import Foundation
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int] = [Int]()
var strings: [String] = [String]()
doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
strings = numbers.map({ (number: Int) -> String in
return "\(number)"
})
print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]
클로저 표현의 간략화
import Foundation
let numbers: [Int] = [0, 1, 2, 3, 4]
// 기본 클로저 표현식 사용
var doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
// 매개변수 및 반환 타입 생략
doubledNumbers = numbers.map({ return $0 * 2 })
print(doubledNumbers) // [0, 2, 4, 6, 8]
// 반환 키워드 생략
doubledNumbers = numbers.map({ $0 * 2 })
print(doubledNumbers) // [0, 2, 4, 6, 8]
// 후행 클로저 사용
doubledNumbers = numbers.map{ $0 * 2 }
print(doubledNumbers) // [0, 2, 4, 6, 8]
클로저의 반복 사용
import Foundation
let evenNumbers: [Int] = [0, 2, 4, 6, 8]
let oddNumbers: [Int] = [0, 1, 3, 5, 7]
let multiplyTwo: (Int) -> Int = { $0 * 2 }
let doubledEvenNumbers = evenNumbers.map(multiplyTwo)
print(doubledEvenNumbers) // [0, 4, 8, 12, 16]
let doubledOddNumbers = oddNumbers.map(multiplyTwo)
print(doubledOddNumbers) // [0, 2, 6, 10, 14]
Filter는 말 그대로 컨테이너 내부의 값을 걸러서 추출하는 역할을 하는 고차함수
맵과 마찬가지로 새로운 컨테이너에 값을 담아 반환해줌
다만 맵처럼 기존 콘텐츠를 변형하는 것이 아니라, 특정 조건에 맞게 걸러내는 역할을 할 수 있다는 점이 다름
filter 함수의 매개변수로 전달되는 함수의 반환 타입은 Bool임
해당 콘텐츠의 값을 갖고 새로운 컨테이너에 포함될 항목이라고 판단하면 true를, 포함하지 않으려면 false를 반환해주면 됨
필터 메서드의 사용
import Foundation
let numbers: [Int] = [0, 1, 2, 3, 4, 5]
let evenNumbers: [Int] = numbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers) // [0, 2, 4]
let oddNumbers: [Int] = numbers.filter{ $0 % 2 == 1 }
print(oddNumbers) // [1, 3, 5]
맵과 필터 메서드의 연계 사용
import Foundation
let numbers: [Int] = [0, 1, 2, 3, 4, 5]
let mappedNumbers: [Int] = numbers.map{ $0 + 3 }
let evenNumbers: [Int] = mappedNumbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers) // [4, 6, 8]
// mappedNumbers를 굳이 여러 번 사용할 필요가 없다면 메서드를 체인처럼 연결하여 사용할 수 있음
let oddNumbers: [Int] = numbers.map{ $0 + 3 }.filter{ $0 % 2 == 1 }
print(oddNumbers) // [3, 5, 7]
스위프트의 리듀스는 두 가지 형태로 구현되어 있음
첫 번째 리듀스는 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환하는 형태
public func reduce<Result>(_ initialResult: Result,
_nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
두 번째 리듀스 메서드는 컨테이너를 순환하며 클로저가 실행되지만 클로저가 따로 결과값을 반환하지 않는 형태. 대신 inout 매개변수를 사용하여 초깃값에 직접 연산을 실행하게 됨
public func reduce<Result>(into initialResult: Result,
_ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows ->
Result
리듀스 메서드의 사용
초기값이 0이고 정수 배열의 모든 값을 더합니다
import Foundation
let numbers: [Int] = [1, 2, 3]
// 첫 번째 형태인 reduce(_:_:) 메서드의 사용
var sum: Int = numbers.reduce(0, { (result: Int, next: Int) -> Int in
print("\(result) + \(next)")
// 0 + 1
// 1 + 2
// 3 + 3
return result + next
})
print(sum) // 6
초기값이 0이고 정수 배열의 모든 값을 뺍니다
let subtract: Int = numbers.reduce(0, { (result: Int, next: Int) -> Int in
print("\(result) - \(next)")
// 0 - 1
// -1 - 2
// -3 - 3
return result - next
})
print(subtract) // -6
초기값이 3이고 정수 배열의 모든 값을 더합니다
let sumFromThree: Int = numbers.reduce(3) {
print("\($0) + \($1)")
// 3 + 1
// 4 + 2
// 6 + 3
return $0 + $1
}
print(sumFromThree) // 9
초기값이 3이고 정수 배열의 모든 값을 뺍니다
var subtractFromThree: Int = numbers.reduce(3) {
print("\($0) - \($1)")
// 3 - 1
// 2 - 2
// 0 - 3
return $0 - $1
}
print(subtractFromThree) // -3
문자열 배열을 reduce(: :) 메서드를 이용해 연결시킵니다
import Foundation
let names: [String] = ["Sunny", "Steven", "Summer", "Kio", "Tak", "Coda"]
let reducedNames: String = names.reduce("yagom 2기 캠퍼들 : ") {
return $0 + ", " + $1
}
print(reducedNames) // yagom 2기 캠퍼들 : , Sunny, Steven, Summer, Kio, Tak, Coda
두 번째 형태인 reduce(into:_:) 메서드의 사용
초기값이 0이고 정수 배열의 모든 값을 더합니다.
첫 번째 리듀스 형태와 달리 클로저의 값을 반환하지 않고
내부에서 직접 이전 값을 변경한다는 점이 다릅니다.
import Foundation
let numbers: [Int] = [1, 2, 3]
var sum = numbers.reduce(into: 0, { (result: inout Int, next: Int) in
print("\(result) + \(next)")
// 0 + 1
// 1 + 2
// 3 + 3
result += next
})
print(sum) // 6
초기값이 3이고 정수 배열의 모든 값을 뺍니다.
첫 번째 리듀스 형태와 달리 클로저의 값을 반환하지 않고
내부에서 직접 이전 값을 변경한다는 점이 다릅니다.
var subtractFromThree = numbers.reduce(into: 3, {
print("\($0) - \($1)")
// 3 - 1
// 2 - 2
// 0 - 3
$0 -= $1
})
print(subtractFromThree) // -3
첫 번째 리듀스 형태와 다르기 때문에 다른 컨테이너 값을 변경하여 넣어줄 수도 있습니다.
이렇게 하면 맵이나 필터와 유사한 형태로 사용할 수도 있습니다.
홀수는 걸러내고 짝수만 두 배로 변경하여 초기값인 [1, 2, 3] 배열에 직접 연산합니다.
guard 문에 next.is가 뭐지??
에러가 나는데 홀.. 🤔
필터와 맵을 사용한 모습
var doubledNumbers = [1, 2] + numbers.filter{ $0.isMultiple(of: 2) }.map { $0 * 2 }
print(doubledNumbers) // [1, 2, 4]
이름을 모두 대문자로 변환하여 초기값인 빈 배열에 직접 연산
let names: [String] = ["Sunny", "Steven", "Summer", "Kio", "Tak", "Coda"]
var upperCasedNames: [String]
upperCasedNames = names.reduce(into: [], {
$0.append($1.uppercased())
})
print(upperCasedNames) // ["SUNNY", "STEVEN", "SUMMER", "KIO", "TAK", "CODA"]
맵을 사용한 모습
var upperCasedNames = names.map { $0.uppercased() }
print(upperCasedNames) // ["SUNNY", "STEVEN", "SUMMER", "KIO", "TAK", "CODA"]
맵, 필터, 리듀스 메서드의 연계 사용
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7]
// 짝수를 걸러내어 각 값에 3을 곱해준 후 모든 값을 더함
var result: Int = numbers.filter{ $0.isMultiple(of: 2) }.map{ $0 * 3 }.reduce(0){
$0 + $1 }
print(result) // 36
for-in 구문 사용시
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7]
var result = 0
for number in numbers {
guard number.isMultiple(of: 2) else {
continue
}
result += number * 3
}
print(result) // 36
import Foundation
enum CoffeeType {
case caffeine, decaffeination, unknown
}
struct coffeeMenu {
let name: String
let coffeeType: CoffeeType
var price: UInt
}
var coffees: [coffeeMenu] = [coffeeMenu]()
coffees.append(coffeeMenu(name: "카페 라떼", coffeeType: .caffeine, price: 4000))
coffees.append(coffeeMenu(name: "따듯한 아메리카노", coffeeType: .caffeine, price: 3500))
coffees.append(coffeeMenu(name: "에티오피아 원두 커피", coffeeType: .decaffeination, price: 3500))
// 카페인이 들어가면서 가격이 4천원 이하인 커피 메뉴 찾기
var result: [coffeeMenu] = coffees.map{ coffeeMenu(name: $0.name, coffeeType: $0.coffeeType, price: $0.price) }
result = result.filter{ $0.coffeeType == .caffeine && $0.price <= 4000 }
let string: String = result.reduce("카페인이 들어가면서 가격이 4천원 이하인 커피 메뉴 ☕️") { $0 + "\n" + "\($1.name) \($1.coffeeType) \($1.price)원"}
print(string)
//카페인이 들어가면서 가격이 4천원 이하인 커피 메뉴 ☕️
//카페 라떼 caffeine 4000원
//따듯한 아메리카노 caffeine 3500원