일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 앱버전구하기
- 앱배포
- FileOwner
- 웹
- jQuery
- 계산기앱만들기
- 자바스크립트
- iOS계산기
- 딩동말씀
- 스위프트
- AJAX
- Swift
- Xcode
- iOS앱배포
- 개발기록
- 스프링
- subscript
- JavaScript
- Xib
- ios
- FLASK
- spring
- 맥
- 파이썬서버
- customclass
- iOS배포
- 계산기앱
- MainScheduler
- Python
- DispatchGroup
- Today
- Total
개발하는 뚝딱이
[swift] 맵, 필터, 리듀스 본문
책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.
고차함수
- 매개변수로 함수를 갖는 함수를 고차함수라고 부름
- 스위프트에 유용한 대표적인 고차함수 ; 맵, 필터, 리듀스 등이 있음
1. 맵
- map
- 자신을 호출할 때 매개변수로 전달된 함수를 실행하여 그 결과를 다시 반환해주는 함수
- 맵은 배열, 딕셔너리, 세트, 옵셔널 등에서 사용 가능
- Sequence, Collection 프로토콜을 따르는 타입과 옵셔널은 모두 맵을 사용할 수 있음
- 기존 데이터를 변형하는데 많은 사용이 됨
- for in 구문과 맵 메서드 사용 비교
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubleNumbers: [Int] = [Int]()
var strings: [String] = [String]()
// for 구문 사용
for number in numbers {
doubleNumbers.append(number*2)
strings.append("\(number)")
}
print(doubleNumbers) // 0, 2, 4, 6, 8
print(strings) // ["0", "1", "2", "3", "4"]
// map 메서드 사용
doubleNumbers = numbers.map({ (number: Int) -> Int in
return number*2
})
strings = numbers.map({ (number: Int) -> String in
return "\(number)"
})
print(doubleNumbers) // 0, 2, 4, 6, 8
print(strings) // ["0", "1", "2", "3", "4"]
- 클로저 표현의 간략화
let numbers: [Int] = [0, 1, 2, 3, 4]
// 기본 클로저 표현식 사용
var doubleNumbers = numbers.map ({ (number: Int) -> Int in
return number * 2
})
// 매개변수 및 반환 타입 생략
doubleNumbers = numbers.map({ return $0 * 2})
print(doubleNumbers)
// 반환 키워드 생략
doubleNumbers = numbers.map({ $0 * 2 })
print(doubleNumbers)
// 후행 클로저 사용
doubleNumbers = numbers.map { $0 * 2 }
print(doubleNumbers)
- 클로저의 반복 사용
let evenNumbers: [Int] = [0, 2, 4, 6, 8]
let oddNumbers: [Int] = [0, 1, 3, 5, 7]
let multiplyTwo: (Int) -> Int = { $0 * 2 }
let doubleEvenNumbers = evenNumbers.map(multiplyTwo)
print(doubleEvenNumbers) // [0, 4, 8, 12, 16]
let doubleOddNumbers = oddNumbers.map(multiplyTwo)
print(doubleOddNumbers) // [0, 2, 6, 10, 14]
- 다양한 컨테이너 타입에서의 맵의 활용
let alphabetDictionary: [String: String] = ["a": "A", "b": "B"]
var keys: [String] = alphabetDictionary.map { (tuple: (String, String)) -> String in
return tuple.0
}
keys = alphabetDictionary.map{ $0.0 }
let values: [String] = alphabetDictionary.map{ $0.1 }
print(keys) // ["b", "a"]
print(values) // ["B", "A"]
var numberSet: Set<Int> = [1, 2, 3, 4, 5]
let resultSet = numberSet.map{ $0 * 2 }
print(resultSet) // [2, 4, 6, 8, 10]
let operationalInt: Int? = 3
let resultInt: Int? = operationalInt.map { $0 * 2 }
print(resultInt) // Optional(6) - 타입캐스팅의 이유로 경고 발생
let range: CountableClosedRange = (0...3)
let resultRange: [Int] = range.map{ $0 * 2 }
print(resultRange) // [0, 2, 4, 6]
2. 필터
- filter
- 특정 조건에 맞게 값을 걸러서 추출하는 역할
- filter 함수의 매개변수로 전달되는 함수의 반환 타입은 Bool
- 해당 콘텐츠의 값을 갖고 새로운 컨테이너에 포함될 항목이라고 판단하면 true, 포함하지 않으려면 false를 반환
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]
- 맵과 필터 메서드의 연계 사용
let numbers: [Int] = [0, 1, 2, 3, 4, 5]
let mappedNumbers: [Int] = numbers.map { $0 + 3 }
let evenNumbers: [Int] = mappedNumbers.filter { $0%2 == 0 }
print(evenNumbers) // [4, 6, 8]
// mappedNumbers를 굳이 여러 번 사용할 필요가 없다면 메서드를 체인처럼 연결하여 사용할 수 있음
let oddNumbers: [Int] = numbers.map{ $0+3 }.filter{ $0%2 == 1}
print(oddNumbers) // [3, 5, 7]
3. 리듀스
- reduce
- 컨테이너 내부의 콘텐츠를 하나로 합하는 기능을 실행하는 고차함수
- 배열이라면 배열의 모든 값을 전달인자로 전달받은 클로저의 연산 결과로 합해줌
- 스위프트의 리듀스는 두 가지 형태
1. 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환하는 형태
- initialResult이라는 이름의 매개변수로 전달되는 값을 통해 초깃값을 지정해줄 수 있음
- nextPartialResult라는 이름의 매개변수로 클로저를 전달받음
- nextPartialResult 클로저의 첫 번째 매개변수는 리듀스 메서드의 initialResult 매개변수를 통해 전달받은 초깃값 또는 이전 클로저의 결괏값이고, 모든 순회가 끝나면 리듀스의 최종 결괏값이 됨
public func reduce<Result>(_ initialResult: Result,
_ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
2. 컨테이너를 순환하며 클로저가 실행되지만 클로저가 따로 결괏값을 반환하지 않는 형태
- 대신 inout 매개변수를 사용하여 초깃값에 직접 연산을 실행하게 됨
- updateAccumulatingResult 매개변수로 전달받는 클로저의 매개변수 중 첫 번째 매개변수를 inout 매개변수로 사용
- updateAccumulatingResult 클로저의 첫 번째 매개변수는 리듀스 메서드의 initialResult 매개변수를 이용해 전달받은 초깃값 또는 이전에 실행된 클로저 때문에 변경되어 있는 결과값임
public func reduce<Result>(into initialResult: Result,
_ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result
- 첫 번째 형태인 reduce(_:_:) 메서드의 사용
let numbers: [Int] = [1, 2, 3]
// 초깃값이 0이고 정수 배열의 모든 값을 더합니다.
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
var subtractFromThree: Int = numbers.reduce(3) {
print("\($0) - \($1)")
// 3 - 1
// 2 - 2
// 0 - 3
return $0 - $1
}
print(subtractFromThree) // -3
// 문자열 배열을 reduce(_:_:) 메서드를 이용해 연결시킵니다.
let names: [String] = ["Chope", "Jay", "Joker", "Nova"]
let reduceNames: String = names.reduce("friend:") {
return $0 + " " + $1
}
print(reduceNames) // friend: Chope Jay Joker Nova
- 두 번째 형태인 reduce(into:_:) 메서드의 사용
let numbers: [Int] = [1, 2, 3]
// 초깃값이 0이고 정수 배열의 모든 값을 더합니다.
// 첫 번째 리듀스 형태와 달리 클로저의 값을 반환하지 않고 내부에서
// 직접 이전 값을 변경한다는 점이 다릅니다.
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] 배열에 직접 연산합니다.
var doubledNumbers: [Int] = numbers.reduce(into: [1, 2]) { (result: inout [Int], next: Int) in
print("result: \(result) next: \(next)")
guard next%2 == 0 else { return }
print("\(result) append \(next)")
// [1, 2] append 2
result.append(next * 2)
}
print(doubledNumbers) // [1, 2, 4]
// 필터와 맵을 사용하는 모습
doubledNumbers = [1, 2] + numbers.filter { $0.isMultiple(of: 2) }.map { $0 * 2 }
print(doubledNumbers) // [1, 2, 4]
// 이름을 모두 대문자로 변환하여 초깃값인 빈 배열에 직접 연산합니다.
let names: [String] = ["Chope", "Jay", "Joker", "Nova"]
var upperCaseNames: [String]
upperCaseNames = names.reduce(into: [], {
$0.append($1.uppercased())
})
print(upperCaseNames) // ["CHOPE", "JAY", "JOKER", "NOVA"]
'swift' 카테고리의 다른 글
[swift] 모나드 (0) | 2020.06.23 |
---|---|
[swift] 타입캐스팅 (0) | 2020.05.17 |
[swift] 옵셔널 체이닝과 빠른 종료 (0) | 2020.05.08 |
[swift] 클로저 (0) | 2020.05.07 |
[swift] 접근제어 (0) | 2020.05.02 |