1. First-Class πŸ‘©β€πŸ’»

1. First-Class Citizen

ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄ λ””μžμΈμ—μ„œ First-Class Citizen(type, object, entity, value)은 λ‹€λ₯Έ entityμ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λͺ¨λ“  μž‘μ—…μ„ μ§€μ›ν•˜λŠ” entity둜 λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°–λŠ”λ‹€.

  • λͺ¨λ“  μ•„μ΄ν…œμ€ ν•¨μˆ˜μ˜ parameters(arguments)κ°€ 될 수 μžˆλ‹€
  • λͺ¨λ“  μ•„μ΄ν…œμ€ ν•¨μˆ˜μ˜ return value κ°€ 될 수 μžˆλ‹€
  • λͺ¨λ“  μ•„μ΄ν…œμ€ μƒμˆ˜ λ˜λŠ” λ³€μˆ˜μ— ν• λ‹Ή 될 수 μžˆλ‹€
  • λͺ¨λ“  μ•„μ΄ν…œμ€ tested for equality κ°€ κ°€λŠ₯ν•˜λ‹€

2. First-Class Function

Computer Science μ—μ„œ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄κ°€ ν•¨μˆ˜λ₯Ό First-Class Citizen으둜 닀루면, First-Class Function을 가지고 μžˆλ‹€κ³  ν•œλ‹€. 이것은 λ‹€μŒμ„ μ˜λ―Έν•œλ‹€.

  • ν•¨μˆ˜κ°€ λ‹€λ₯Έ ν•¨μˆ˜μ˜ arguments 둜 전달될 수 μžˆλ‹€
  • ν•¨μˆ˜λ₯Ό λ‹€λ₯Έ ν•¨μˆ˜μ˜ return value 둜 λ°˜ν™˜ν•  수 μžˆλ‹€
  • ν•¨μˆ˜λ₯Ό μƒμˆ˜ λ˜λŠ” λ³€μˆ˜μ— ν• λ‹Ή ν•˜κ±°λ‚˜ Data Structures 에 μ €μž₯ν•  수 μžˆλ‹€
  • ν•¨μˆ˜μ˜ equality 비ꡐ가 κ°€λŠ₯ν•˜λ‹€

λ˜ν•œ ν”„λ‘œκ·Έλž˜λ° 이둠에 따라 First-Class Function 은 Anonymous Functions(Function literals)λ₯Ό μš”κ΅¬ν•˜κΈ°λ„ ν•œλ‹€. First-Class Function 이 μžˆλŠ” ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œ ν•¨μˆ˜μ˜ 이름은 νŠΉλ³„ν•œ μƒνƒœκ°€ μ—†λ‹€. Integer νƒ€μž…μ˜ λ³€μˆ˜λ₯Ό 닀루듯 ν•¨μˆ˜ μ—­μ‹œ Function νƒ€μž…μ˜ 일반 λ³€μˆ˜μ²˜λŸΌ μ·¨κΈ‰λœλ‹€.

First-Class Function은 Functional Programming의 ν•„μˆ˜μš”μ†Œμ΄λ©°, Higher-order FunctionsλŠ” Functional programming의 ν‘œμ€€κ³Όλ„ κ°™λ‹€.

Higher-order Functions 의 예둜 Map ν•¨μˆ˜λ₯Ό μ‚΄νŽ΄λ³΄μž. Map ν•¨μˆ˜λŠ” Functionκ³Ό listλ₯Ό arguments둜 μ·¨ν•˜λ©°, list 의 각 member 에 ν•¨μˆ˜λ₯Ό 적용 ν•œ listλ₯Ό λ°˜ν™˜ν•œλ‹€.

let someIntArray: [Int] = [1, 2, 4, 5, 8, 11, 15]
let doubleIntArray: [Int] = someIntArray.map { $0 * 2 }
print(doubleIntArray)   // [2, 4, 8, 10, 16, 22, 30]

즉, ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄κ°€ Map을 μ§€μ›ν•˜λ €λ©΄, λ°˜λ“œμ‹œ ν•¨μˆ˜κ°€ λ‹€λ₯Έ ν•¨μˆ˜μ˜ arguments둜 전달될 수 μžˆμ–΄μ•Ό ν•œλ‹€.


First-Class Function 을 μ§€μ›ν•˜λŠ” ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œλŠ” Nested Functions λ˜λŠ” Anonymous Functionsκ°€ body μ™ΈλΆ€μ˜ λ³€μˆ˜(non-local variables)λ₯Ό μ°Έμ‘°ν•˜λŠ” 것이 μžμ—°μŠ€λŸ¬μš΄ 반면, 그렇지 μ•Šμ€ μ–Έμ–΄λŠ” ν•¨μˆ˜λ₯Ό arguments둜 μ „λ‹¬ν•˜κ±°λ‚˜ return value둜 λ°˜ν™˜ν•˜λŠ”λ° 어렀움이 μžˆλ‹€.

λ”°λΌμ„œ 초기 Imperative languages(λͺ…λ Ήν˜• μ–Έμ–΄)μ—μ„œλŠ” 이λ₯Ό νšŒν”Όν•˜κΈ° μœ„ν•΄ ν•¨μˆ˜λ₯Ό return types 둜 ν—ˆμš©ν•˜μ§€ μ•ŠμŒμ€ λ¬Όλ‘ , Nested Functions λ‚˜ non-local variables 등을 λͺ¨λ‘ ν—ˆμš©ν•˜μ§€ μ•Šμ•˜λ‹€. μ΄λŸ¬ν•œ μ–Έμ–΄μ—μ„œ ν•¨μˆ˜λŠ” Second-Class citizen 이닀.
이후 μ΅œμ‹  μ–Έμ–΄μ—μ„œ First-Class Function 을 적절히 μ§€μ›ν•˜κΈ° μœ„ν•΄ ν•¨μˆ˜μ— λŒ€ν•œ μ°Έμ‘°λ₯Ό bare function pointer λŒ€μ‹  Closure 둜 μ²˜λ¦¬ν•˜κ²Œλ˜μ—ˆκ³ , λ”°λΌμ„œ Garbage Collection μ—­μ‹œ ν•„μˆ˜ μš”μ†Œκ°€ λœλ‹€.

First-Class Function은 ν•¨μˆ˜μ— λŒ€ν•œ μ°Έμ‘°λ₯Ό bare function pointer λŒ€μ‹  Closures둜 μ²˜λ¦¬ν•œλ‹€. λ”°λΌμ„œ Garbage Collection이 λ°˜λ“œμ‹œ ν•„μš”ν•˜λ‹€.

참고둜 C 언어와 같이 ν•¨μˆ˜κ°€ First-Class Citizen 이 μ•„λ‹Œ μ–Έμ–΄λŠ” function pointers λ˜λŠ” delegates 와 같은 κΈ°λŠ₯을 μ΄μš©ν•΄ Higher-order function 문법을 μž‘μ„±ν•  수 μžˆλ„λ‘ ν•œλ‹€. ν•˜μ§€λ§Œ μ–Έμ–΄ μžμ²΄κ°€ First-Class Function 을 μ§€μ›ν•˜λŠ” 것이 μ•„λ‹ˆλ―€λ‘œ First-Class Citizen 이 λ˜λŠ” 것은 μ•„λ‹ˆλ‹€.


2. Higher-order Function Examples πŸ‘©β€πŸ’»

1. TypeScript

twice와 plusThreeλΌλŠ” ν•¨μˆ˜κ°€ μžˆλ‹€.

const twice = (f: Function) => {
  return (x: number) => f(f(x))
}
const plusThree = (i: number) => i + 3


twice ν•¨μˆ˜λŠ” μ•„λž˜μ™€ 같이 Body λ₯Ό κ°μ‹ΈλŠ” { }와 return ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•  수 μžˆλ‹€.

const twice = (f: Function) => (x: number) => f(f(x))
const plusThree = (i: number) => i + 3
  • plusThree: number νƒ€μž…μ˜ argumentλ₯Ό λ°›μ•„ 3을 더해 number νƒ€μž…μ„ λ°˜ν™˜ν•œλ‹€.
  • twice: Function νƒ€μž…μ˜ argumentλ₯Ό λ°›μ•„ (x: number) => f(f(x)) ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•œλ‹€.
  • f(x)둜 μ „λ‹¬λ˜λŠ” ν•¨μˆ˜λŠ” <Number>(x: number) => number νƒ€μž…μ˜ ν•¨μˆ˜μ΄λ©°, parameter 와 return type 이 λ™μΌν•˜λ―€λ‘œ μž¬κ·€κ°€ κ°€λŠ₯ν•˜λ‹€. λ”°λΌμ„œ f(f(x))λŠ” argument둜 μž…λ ₯된 ν•¨μˆ˜λ₯Ό μž¬κ·€λ₯Ό μ΄μš©ν•΄ 2번 μ‹€ν–‰ν•˜λŠ” ν•¨μˆ˜λ‹€.


참고둜 TypeScriptλŠ” ν•¨μˆ˜μ˜ νƒ€μž…μ„ λͺ…μ‹œν•  λ•Œ λ‹€μŒ 두 가지 λ°©μ‹μ˜ typealiasλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

  • GenericFunc
    type GenericFunc = <Number>(x: number) => number
    const twice = (f: GenericFunc) => (x: number) => f(f(x))
    
  • GenericType
    type GenericType<Number> = (x: number) => number
    const twice = (f: GenericType<number>) => (x: number) => f(f(x))
    


두 ν•¨μˆ˜λ₯Ό chainingν•΄ someFunctionμ΄λΌλŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“€κ³ , 이λ₯Ό 싀행해보면 λ‹€μŒκ³Ό κ°™λ‹€.

const twice = (f: Function) => (x: number) => f(f(x))
const plusThree = (i: number) => i + 3
const someFunction = twice(plusThree)

console.log(someFunction(7))   // 13   (7 + 3) + 3
console.log(someFunction(9))   // 15   (9 + 3) + 3
console.log(someFunction(12))  // 18   (12 + 3) + 3


2. Swift

1 ) Function Declarations

func twice(_ f: @escaping (Int) -> Int) -> (Int) -> Int {
    return { (x: Int) in
        f(f(x))
    }
}


twice(_:) ν•¨μˆ˜λŠ” μ•„λž˜μ™€ 같이 arguments 와 return ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•  수 μžˆλ‹€.
(TypeScript 와 달리 Body λ₯Ό κ°μ‹ΈλŠ” { }λŠ” μƒλž΅ν•  수 μ—†λ‹€.)

func twice(_ f: @escaping (Int) -> Int) -> (Int) -> Int {
    { f(f($0)) }
}


μœ„ twice 의 parameter type κ³Ό return type 이 보기 νž˜λ“€λ‹€λ©΄ typealiasλ₯Ό μ‚¬μš©ν•œ μ•„λž˜ μ½”λ“œλ₯Ό 보도둝 ν•˜μž.

typealias IntToInt = (Int) -> Int

func twice(_ f: @escaping intToInt) -> intToInt {
  { f(f($0)) }
}


이제 plusThree λ₯Ό μΆ”κ°€ν•˜κ³  ν•¨μˆ˜λ₯Ό chaining μ‹œμΌœ Swift λ²„μ „μ˜ someFunction을 μ™„μ„±ν•΄λ³΄μž.

func twice(_ f: @escaping (Int) -> Int) -> (Int) -> Int {
  { f(f($0)) }
}

func plusThree(_ i: Int) -> Int {
    i + 3
}
let someFunction = twice(plusThree(_:))

print(someFunction(7))  // 13   (7 + 3) + 3
print(someFunction(9))  // 15   (9 + 3) + 3
print(someFunction(12)) // 18   (12 + 3) + 3


2 ) Function Expressions

μœ„ 1κ³Ό λ™μΌν•œ λ‘œμ§μ„ Expressions λ°©μ‹μœΌλ‘œ μž‘μ„±ν•΄λ³΄μž.

let twice = { (f: @escaping (Int) -> Int) in
  { f(f($0)) }
}


μƒμˆ˜λ‚˜ λ³€μˆ˜μ˜ νƒ€μž…μ„ 미리 지정해 twice λ₯Ό λ‹€μŒκ³Ό 같이 μž‘μ„±ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.

let twice: (@escaping (Int) -> Int) -> (Int) -> Int = { f in
    { f(f($0)) }
}


λ§ˆμ°¬κ°€μ§€λ‘œ typealiasλ₯Ό μ‚¬μš©ν•΄ λ‹€μŒκ³Ό 같이 μž‘μ„±ν•  수 μžˆλ‹€.

typealias IntToInt = (Int) -> Int

let twice = { (f: @escaping intToInt) in
    { f(f($0)) }
}
typealias IntToInt = (Int) -> Int

let twice: (@escaping intToInt) -> intToInt = { f in
  { f(f($0)) }
}


λ§ˆμ°¬κ°€μ§€λ‘œ ν•¨μˆ˜λ₯Ό chaining μ‹œμΌœ Closures λ₯Ό μ΄μš©ν•œ someFunction을 μ™„μ„±ν•΄λ³΄μž.

let twice: (@escaping (Int) -> Int) -> (Int) -> Int = { f in
  { f(f($0)) }
}

let plusThree: (Int) -> Int = { $0 + 3 }
let someFunction = twice(plusThree)

print(someFunction(7))  // 13   (7 + 3) + 3
print(someFunction(9))  // 15   (9 + 3) + 3
print(someFunction(12)) // 18   (12 + 3) + 3

3. Higher-order Functions πŸ‘©β€πŸ’»

1. forEach

λ‹€μŒμ€ Swift documentation 의 Instance Method forEach(_:)의 μ„€λͺ…이닀.

func forEach(_ body: (Self.Element) throws -> Void) rethrows

Link: Apple Developer Documentation


그리고 forEach 의 νŠΉμ§•μ€ λ‹€μŒκ³Ό κ°™λ‹€.

  • Collection 의 λͺ¨λ“  elements λ₯Ό μˆœν™˜ν•  뿐 Return Valueκ°€ μ—†λ‹€.
  • continue, break 같은 Control Transfer Statements λ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€. 였직 return만 μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.


Collection 을 μˆœν™˜ν•˜λŠ” 고전적인 λ°©λ²•μœΌλ‘œ For-In Loops κ°€ μžˆλ‹€.

let numbers: [Int] = [2, 5, 3, 9, 11, 14]

for number in numbers {
    number.isMultiple(of: 2) ? print("\(number) is even") : print("\(number) is odd")
}

forEach λŠ” For-In Loops 와 λ™μΌν•œ λ‘œμ§μ„ μˆ˜ν–‰ν•  수 μžˆλ‹€.

numbers.forEach { $0.isMultiple(of: 2) ? print("\($0) is even") : print("\($0) is odd") }
2 is even
5 is odd
3 is odd
9 is odd
11 is odd
14 is even


forEach 와 For-In Loops 의 차이점

  • For-In Loops λŠ” body λ‚΄μ—μ„œ continue, break와 같은 Control Transfer Statements λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
let anotherNumbers: [Int?] = [2, 5, nil, 9, 11, nil, 6, nil, 14]

for number in anotherNumbers {
    guard let number = number else {
        print("Found nil")
        continue
    }
    print("The double of \(number) is \(number * 2)")
}
The double of 2 is 4
The double of 5 is 10
Found nil
The double of 9 is 18
The double of 11 is 22
Found nil
The double of 6 is 12
Found nil
The double of 14 is 28


let anotherNumbers: [Int?] = [2, 5, nil, 9, 11, nil, 6, nil, 14]

for number in anotherNumbers {
    guard let number = number else {
        print("Found nil")
        break
    }
    print("The double of \(number) is \(number * 2)")
}
The double of 2 is 4
The double of 5 is 10
Found nil


  • 반면 forEach λŠ” return만 μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.
anotherNumbers.forEach { number in
    guard let number = number else {
        print("Found nil")
        continue    // 'continue' is only allowed inside a loop
    }
    print("The double of \(number) is \(number * 2)")
}
anotherNumbers.forEach { number in
    guard let number = number else {
        print("Found nil")
        return
    }
    print("The double of \(number) is \(number * 2)")
}
The double of 2 is 4
The double of 5 is 10
Found nil
The double of 9 is 18
The double of 11 is 22
Found nil
The double of 6 is 12
Found nil
The double of 14 is 28
  • For-In Loops 와 forEach λͺ¨λ‘ ν•¨μˆ˜μ˜ Return Valueκ°€ μ—†λ‹€.
  • For-In Loops 와 forEach λŠ” λΉ„μŠ·ν•˜μ§€λ§Œ, forEachλŠ” loopsκ°€ μ•„λ‹ˆλ―€λ‘œ continueλ‚˜ breakκ³Ό 같은 Control Transfer Statements λ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€.
  • μ‹€μ œλ‘œ forEach λŠ” Collection 을 μˆœν™˜ν•˜μ§€λ§Œ forEach 의 argument둜 μ „λ‹¬λ˜λŠ” trailing closure의 μž…μž₯μ—μ„œλŠ” μ—¬λŸ¬ 번 호좜될 뿐 loopsκ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€.
  • λ”°λΌμ„œ, ν˜„μž¬ 호좜된 closureλ₯Ό μ’…λ£Œν•˜κΈ° μœ„ν•œ return ν‚€μ›Œλ“œλ§Œ ν—ˆμš©λœλ‹€. λ˜ν•œ μ—¬κΈ°μ„œ μ‚¬μš©λ˜λŠ” return ν‚€μ›Œλ“œλŠ” ν˜„μž¬ 호좜된 closure λ₯Ό μ’…λ£Œν•˜λŠ” 것일 뿐 forEach μˆœν™˜ 자체λ₯Ό μ’…λ£Œν•˜μ§€λŠ” μ•ŠλŠ”λ‹€ (forEach μ—μ„œ return 은 For-In Loops 의 continue 와 같은 역할을 ν•œλ‹€.).

2. map

1 ) Array.map(_:)

λ‹€μŒμ€ Swift documentation 의 Instance Method map(_:)의 μ„€λͺ…이닀.

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

Link: Apple Developer Documentation

map ν•¨μˆ˜λŠ” κ°€μž₯ 유λͺ…ν•œ Higher-order Function 으둜 Collection 의 λͺ¨λ“  elements 에 λ‘œμ§μ„ μˆ˜ν–‰ ν›„ new Collection을 λ°˜ν™˜ν•œλ‹€.


λ‹€μŒμ€ κ°€μž₯ 기본적인 map ν•¨μˆ˜μ˜ μž‘λ™μ΄λ‹€. Original Collection의 λͺ¨λ“  elements에 2λ₯Ό κ³±ν•œ λ‹€μŒ new Collection을 λ°˜ν™˜ν•œλ‹€.

let numbers: [Int] = [2, 5, 3, 9, 11, 14]

var doubled: [Int] = [Int]()
doubled = numbers.map { $0 * 2 }
print(doubled)          // [4, 10, 6, 18, 22, 28]


new Collection을 λ°˜ν™˜ν•˜λŠ” κ²ƒμ΄λ―€λ‘œ, Original Collectionκ³Ό Data Type이 같을 ν•„μš”κ°€ μ—†λ‹€.

let degrees = [20, 45, 180, 360, 185]

let rads = degrees.map { Double($0) * Double.pi / 180.0 }
let tenThousand: Double = pow(10, 4)

rads.forEach { print("\(round($0 * tenThousand) / tenThousand) radian") }
0.3491 radian
0.7854 radian
3.1416 radian
6.2832 radian
3.2289 radian


2 ) Set.map(_:)

Set 의 map 은 Array 의 map κ³Ό κ°™λ‹€.

let people: Set<String> = ["Thomasin McKenzie", "Anya Taylor-Joy", "Matt Smith", "Diana Rigg", "Rita Tushingham"]
let firstName = people.map { $0.split(separator: " ")[0] }
let lastName = people.map { $0.split(separator: " ")[1] }

print(firstName)    // ["Anya", "Rita", "Thomasin", "Matt", "Diana"]
print(lastName)     // ["Taylor-Joy", "Tushingham", "McKenzie", "Smith", "Rigg"]


3 ) Dictionary.map(_:)

Dictionary λŠ” Key: Value ꡬ쑰이기 λ•Œλ¬Έμ— Array λ‚˜ Set κ³ΌλŠ” 쑰금 λ‹€λ₯Έ λͺ¨μŠ΅μ„ 보인닀.

let info: [String: String] = ["name": "andrew",
                              "city": "berlin",
                              "job": "developer",
                              "hobby": "computer games"]

let keys = info.map { $0.key }
let values = info.map { $0.value }

print(keys)     // ["city", "name", "hobby", "job"]
print(values)   // ["berlin", "andrew", "computer games", "developer"]


λ§Œμ•½, map μ—μ„œ key 와 value λ₯Ό κ΅¬λΆ„ν•˜μ§€ μ•Šκ³  μ ‘κ·Όν•˜λ©΄, tuple둜 μ ‘κ·Όν•˜κ²Œλœλ‹€.

let tupleData = info.map { $0 }
print(type(of: tupleData))              // Array<(key: String, value: String)>
print(type(of: tupleData[0]))           // (key: String, value: String)
print(type(of: tupleData[0].key))       // String
print(type(of: tupleData[0].value))     // String

tupleData.forEach {
    print($0)
}
(key: "job", value: "developer")
(key: "name", value: "andrew")
(key: "hobby", value: "computer games")
(key: "city", value: "berlin")

μœ„μ—μ„œ λ³Ό 수 μžˆλ“―μ΄ info.map { $0 }의 Return Type 은 (key: String, value: String)νƒ€μž…μ˜ tuple 을 μ €μž₯ν•˜λŠ” 배열을 λ§Œλ“€μ–΄ λ°˜ν™˜ν•œλ‹€.


let updatedKeysAndValues = info.map { ($0.uppercased(), $1.capitalized) }
print(type(of: updatedKeysAndValues))       // Array<(String, String)>
print(type(of: updatedKeysAndValues[0]))    // (String, String)

updatedKeysAndValues.forEach {
    print($0)
}
("CITY", "Berlin")
("NAME", "Andrew")
("JOB", "Developer")
("HOBBY", "Computer Games")


λ‹€μŒκ³Ό 같이 label 을 ν¬ν•¨ν•˜λ„λ‘ κ°€κ³΅ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.

let anotherKeysAndValues = info.map { (list: $0.uppercased(), userInfo: $1.capitalized) }
print(type(of: anotherKeysAndValues))       // Array<(list: String, userInfo: String)>
print(type(of: anotherKeysAndValues[0]))    // (list: String, userInfo: String)

anotherKeysAndValues.forEach {
    print($0)
}
(list: "CITY", userInfo: "Berlin")
(list: "JOB", userInfo: "Developer")
(list: "NAME", userInfo: "Andrew")
(list: "HOBBY", userInfo: "Computer Games")


이제 μœ„ tuple 을 Dictionary의 initializer λ₯Ό μ΄μš©ν•΄ λ‹€μ‹œ Dictionary 둜 λ§Œλ“€μ–΄λ³΄μž

let capitalizedInfo = Dictionary(uniqueKeysWithValues: anotherKeysAndValues)
print(type(of: capitalizedInfo))    // Dictionary<String, String>
print(capitalizedInfo)  // ["NAME": "Andrew", "HOBBY": "Computer Games", "CITY": "Berlin", "JOB": "Developer"]


λ˜λŠ” μ•„λž˜μ„œ 배울 reduceλ₯Ό μ΄μš©ν•˜λ©΄ map이 배열을 λ°˜ν™˜ν•œ 값을 κ·ΈλŒ€λ‘œ Higher-order Functions의 chaining을 μ΄μš©ν•΄ λ‹€μŒκ³Ό 같이 μž‘μ„±ν•˜λŠ” 것이 κ°€λŠ₯ν•˜λ‹€.

let capitalizedInfo = info.lazy
    .map { ($0.uppercased(), $1.capitalized) }
    .reduce(into: [String: String]()) { $0[$1.0] = $1.1 }


μœ„ reduceκ°€ μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ” 것인지 아직은 μ΄ν•΄ν•˜κΈ°κ°€ μ–΄λ €μšΈ 것이닀. reduce의 λ§ˆμ§€λ§‰ argument의 trailing closure의 λ‚΄λΆ€μ—μ„œ $0, $1.0, $1.1을 좜λ ₯해보면 λ‹€μŒκ³Ό κ°™λ‹€.

let capitalizedInfo = info.lazy
    .map { ($0.uppercased(), $1.capitalized) }  // return tuple array
    .reduce(into: [String: String]()) {
        let str = """
                $0: \($0)
                $1.0: \($1.0),  $1.1: \($1.1)
                
                """
        print(str)
        return $0[$1.0] = $1.1
    }
$0: [:]
$1.0: HOBBY,  $1.1: Computer Games

$0: ["HOBBY": "Computer Games"]
$1.0: NAME,  $1.1: Andrew

$0: ["HOBBY": "Computer Games", "NAME": "Andrew"]
$1.0: JOB,  $1.1: Developer

$0: ["HOBBY": "Computer Games", "NAME": "Andrew", "JOB": "Developer"]
$1.0: CITY,  $1.1: Berlin

μœ„ κ²½μš°λŠ” map μ—μ„œλŠ” 데이터λ₯Ό κ°€κ³΅λ§Œ ν•˜κ³ , reduce μ—μ„œ 데이터λ₯Ό λͺ¨μ•˜λ‹€. 그리고 이λ₯Ό lazy λ₯Ό μ΄μš©ν•΄μ„œ chaining ν–ˆλ‹€. 사싀 reduce κ°€ λŒ€λΆ€λΆ„μ˜ μ˜ˆμ œμ—μ„œ Integer Arrayλ₯Ό μ‚¬μš©ν•΄ 값을 λ”ν•˜κ±°λ‚˜ κ³±ν•˜λŠ” λ“±μ˜ λ‘œμ§μ„ λ³΄μ—¬μ£Όλ‹€λ³΄λ‹ˆ Collection 이 μ•„λ‹Œ 단일 값을 λ°˜ν™˜ν•΄μ•Ό ν•˜λŠ” κ²ƒμœΌλ‘œ μƒκ°ν•˜κΈ° μ‰½μ§€λ§Œ 단일 Array λ˜λŠ” 단일 Dictionary 와 같이 ν•˜λ‚˜μ˜ Collection νƒ€μž… μ•ˆμ— 값을 λˆ„μ μ‹œμΌœ λ°˜ν™˜ν•˜λŠ” 것 μ—­μ‹œ κ°€λŠ₯ν•˜λ‹€(Array, Dictionary μ—­μ‹œ ν•˜λ‚˜μ˜ 값이닀.).

λ”°λΌμ„œ 단 ν•œ 번의 reduce 둜 λ‹€μŒκ³Ό 같이 μž‘μ„±ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.

let capitalizedInfo = info.lazy
    .map { ($0.uppercased(), $1.capitalized) }
    .reduce(into: [String: String]()) { $0[$1.0] = $1.1 }
// ["NAME": "Andrew", "JOB": "Developer", "HOBBY": "Computer Games", "CITY": "Berlin"]

let capitalizedInfo = info.reduce(into: [String: String]()) {
    $0[$1.key.uppercased()] = $1.value.capitalized
}
// ["NAME": "Andrew", "JOB": "Developer", "HOBBY": "Computer Games", "CITY": "Berlin"]


4 ) Dictionary.mapValues(_:)

일반적으둜 Dictionaryλ₯Ό μ‚¬μš©ν•  λ•Œ KeyλŠ” λ³€κ²½ν•˜μ§€ μ•ŠλŠ”λ‹€. 이럴 λ•Œ μœ μš©ν•œ ν•¨μˆ˜κ°€ mapValuesλ‹€.

let updatedValues = info.mapValues { $0.capitalized }
print(updatedValues)    // ["hobby": "Computer Games", "job": "Developer", "city": "Berlin", "name": "Andrew"]

Dictionay에 map ν•¨μˆ˜λ₯Ό μ μš©ν•  λ•Œ μœ μš©ν•œ 각각의 case λ₯Ό μ •λ¦¬ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

  • someDictionary.map : (μ΅œμ’… 결과물이 Array<Any>) λ˜λŠ” (Key의 변경이 ν•„μš”ν•  λ•Œ)
    (Dictionary 둜 λ°˜ν™˜ν•˜κΈ° μœ„ν•΄μ„œλŠ” μΆ”κ°€λ‘œ reduce κ°€ ν•„μš”ν•˜λ‹€.)
  • someDictionary.reduce : (μ΅œμ’… 결과물이 Dictionary) & (Key의 변경이 ν•„μš”ν•  λ•Œ)
  • someDictionary.mapValues : (μ΅œμ’… 결과물이 Dictionary) & (Value의 λ³€κ²½λ§Œ ν•„μš”ν•  λ•Œ)

  • someDictionary.keys.map : (μ΅œμ’… 결과물이 Array) & (Key만 ν•„μš”ν•  λ•Œ)
  • someDictionary.values.map : (μ΅œμ’… 결과물이 Array) & (Value만 ν•„μš”ν•  λ•Œ)

3. compactMap

Collection 이 nil을 ν¬ν•¨ν•˜κ³  μžˆλŠ” 경우 μœ μš©ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλŠ”, map κ³Ό 맀우 μœ μ‚¬ν•œ compactMap이 μžˆλ‹€.

λ‹€μŒμ€ Swift documentation 의 Instance Method compactMap(_:)의 μ„€λͺ…이닀.

func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

Link: Apple Developer Documentation


1 ) Optional Collection with map

let numbersWithNil: [Int?] = [5, 15, nil, 3, 9, 12, nil, nil, 17, nil]


μœ„ nil이 ν¬ν•¨λœ Collection에 map ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄λ³΄μž.

let doubledNums = numbersWithNil.map { $0 * 2 } // error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'

numbersWithNil이 μ €μž₯ν•˜λŠ” 데이터 νƒ€μž…μ€ Int?μ΄λ―€λ‘œ unwrapping을 ν•˜μ§€ μ•ŠμœΌλ©΄ μ‚°μˆ  연산을 ν•  수 μ—†μ–΄μ„œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

let doubledNums = numbersWithNil.map { $0! * 2 } // Fatal error: Unexpectedly found nil while unwrapping an Optional value

unwrapping 을 ν–ˆμ§€λ§Œ 또 λ‹€λ₯Έ μ—λŸ¬κ°€ λ°œμƒν–ˆλ‹€. λ°”λ‘œ Collection 의 element κ°€ nil인 μˆœκ°„ nil! * 2 연산을 μ‹œλ„ν•΄ Runtime Error κ°€ λ°œμƒν•œλ‹€.


λ”°λΌμ„œ λ‹€μŒκ³Ό 같이 Type-Safeν•œ μ½”λ“œλ₯Ό μœ„ν•΄ nil check λ₯Ό ν•΄μ€˜μ•Όν•œλ‹€.

let doubledNums = numbersWithNil.map { (number) -> Int? in
    guard let number = number else { return nil }
    return number * 2
}

μœ„ λ‘œμ§μ„ Ternary Operatorλ₯Ό μ΄μš©ν•΄ μ΅œμ ν™” ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

let doubledNums = numbersWithNil.map { $0 != nil ? $0! * 2 : nil }
print(type(of: doubledNums))    // Array<Optional<Int>>
print(doubledNums)              // [Optional(10), Optional(30), nil, Optional(6), Optional(18), Optional(24), nil, nil, Optional(34), nil]


2 ) Optional Collection with compactMap

let doubledNumsWithoutNil = numbersWithNil.compactMap { $0 != nil ? $0! * 2 : nil }
print(type(of: doubledNumsWithoutNil))  // Array<Int>
print(doubledNumsWithoutNil)            // [10, 30, 6, 18, 24, 34]

compactMap 을 μ‚¬μš©ν•˜λ”λΌλ„ Type-Safeν•œ μ½”λ“œλ₯Ό μœ„ν•΄ nil checkλŠ” λ°˜λ“œμ‹œ ν•΄μ€˜μ•Όν•œλ‹€(filter λ‹€μŒμ— map 을 chaining ν•˜λŠ” κ²ƒμ²˜λŸΌ compactMap λ‹€μŒμ— map 을 chaining 해도 λ˜μ§€λ§Œ, 그러면 ꡳ이 compactMap을 μ“Έ μ΄μœ κ°€ 쀄어든닀).

ν•˜μ§€λ§Œ Original Collection 의 nil을 κ·ΈλŒ€λ‘œ ν¬ν•¨ν•˜λŠ” map κ³Ό 달리 compactMap 은 Optional elementsλ₯Ό μ œκ±°ν•˜κ³ , unwrapping된 Collection 을 λ°˜ν™˜ν•œλ‹€. λ”°λΌμ„œ, nil의 숫자만큼 Collection 의 길이 μ—­μ‹œ 쀄어든닀.

즉, compactMap은 λ‹€μŒ μ½”λ“œλ₯Ό μ••μΆ•ν•œ 것과 κ°™λ‹€.

let doubledNumsWithoutNil = numbersWithNil
        .map { $0 != nil ? $0! * 2 : nil }
        .filter { $0 != nil }
        .map { $0! }

print(type(of: doubledNumsWithoutNil))  // Array<Int>
print(doubledNumsWithoutNil)            // [10, 30, 6, 18, 24, 34]


3 ) Optional Collection with default value

Optional Collection 이라고 무쑰건 compactMap 을 μ‚¬μš©ν•΄μ„œλŠ” μ•ˆ λœλ‹€. nil을 μ œκ±°ν•˜μ§€ μ•Šκ³  λ‚¨κ²¨λ‘κ±°λ‚˜, default value 처리λ₯Ό ν•΄μ•Όν•  μˆ˜λ„ μžˆλ‹€.

λ§Œμ•½ nil을 default value 둜 μ²˜λ¦¬ν•˜λ©΄ λ°˜ν™˜λœ Collection 은 더 이상 nil을 ν¬ν•¨ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ compactMap κ³Ό map 은 λ™μΌν•˜κ²Œ μž‘λ™ν•œλ‹€. λ”°λΌμ„œ 이 경우 ꡳ이 compactMap 을 μ“Έ ν•„μš”κ°€ μ—†λ‹€. map μ—­μ‹œ nil이 제거된 new Collection을 λ°˜ν™˜ν•˜λ―€λ‘œ, Swift λŠ” 이λ₯Ό μΆ”λ‘ ν•΄ unwrapping된 Collection 을 λ°˜ν™˜ν•œλ‹€.

let withDefaultValue = numbersWithNil.compactMap { $0 != nil ? $0! * 2 : -1 }
print(type(of: withDefaultValue))   // Array<Int>
print(withDefaultValue)             // [10, 30, -1, 6, 18, 24, -1, -1, 34, -1]
let withDefaultValue = numbersWithNil.map { $0 != nil ? $0! * 2 : -1 }
print(type(of: withDefaultValue))   // Array<Int>
print(withDefaultValue)             // [10, 30, -1, 6, 18, 24, -1, -1, 34, -1]

map 을 μ‚¬μš©ν–ˆμ§€λ§Œ nil을 default value둜 μ²˜λ¦¬ν–ˆκΈ° λ•Œλ¬Έμ— Swift λŠ” 이λ₯Ό μΆ”λ‘ ν•΄ unwrapping된 new Collection 을 λ°˜ν™˜ν•œλ‹€.
단, default value λ₯Ό μ‚¬μš©ν•  λ•Œ μ£Όμ˜ν•΄μ•Ό ν•  것은 주어진 default value κ°€ 전체 μ•± λ˜λŠ” κ΅¬ν˜„ 쀑인 λ‘œμ§μ— side effectλ₯Ό μΌμœΌν‚€μ§€ μ•ŠλŠ” 값을 μ„ νƒν•΄μ•Όν•œλ‹€.


4 ) Application of compactMap

compactMap 의 κ°€μž₯ μœ μš©ν•œ 점은 λ‹€μŒκ³Ό 같은 μ½”λ“œλ₯Ό 맀우 κ°„λž΅ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆλ‹€λŠ” 것이닀.

let coins = ["1", "5", "$", "10", "6"]

var validCoins: [Int] = []
for coin in coins {
    guard let coin = Int(coin) else { continue }
    validCoins.append(coin)
}

print(validCoins)   // [1, 5, 10, 6]
let validCoins = coins.compactMap { Int($0) }
print(validCoins)   // [1, 5, 10, 6]

이게 μ „λΆ€λ‹€! Type-Safeν•œ μ½”λ“œλ₯Ό μœ„ν•΄ μ‚¬μš©λ˜λŠ” guard let, if let을 μ‚¬μš©ν•œ μ—¬λŸ¬ μ€„μ˜ μ½”λ“œλ₯Ό compactMap 은 맀우 κ°„λ‹¨ν•˜κ²Œ μ²˜λ¦¬ν•œλ‹€.


compactMap 은 .map { (YOUR_TYPE_SAFE_CODE) } .filter { $0 != nil } .map { $0! }λ₯Ό μ••μΆ•ν•œ 것이닀. λ”°λΌμ„œ, Collection μ—μ„œ nil을 μ œκ±°ν•˜κ³  non-nil만 μ–»κ³ μž ν•  λ•Œ μœ μš©ν•˜λ‹€.

4. flatMap

λ‹€μŒμ€ Swift documentation 의 Instance Method flatMap(_:) 의 μ„€λͺ…이닀.

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

Link: Apple Developer Documentation

flatMap 은 2D Arrayλ₯Ό 1D Array둜 λ°”κΎΌλ‹€. 즉, Collection μ•ˆμ— Collection이 μžˆμ„ λ•Œ μœ μš©ν•˜λ‹€.


1 ) 2D Array to 1D Array

let marks = [[3, 4, 5], [2, 5, 3], [1, 2, 2], [5, 5, 4], [3, 5, 3]]
  • For-In Loops
var allMarks: [Int] = []
for marksArray in marks {
    allMarks += marksArray
}
print(allMarks) // [3, 4, 5, 2, 5, 3, 1, 2, 2, 5, 5, 4, 3, 5, 3]
  • flatMap
let allMarks = marks.flatMap { $0 }
print(allMarks) // [3, 4, 5, 2, 5, 3, 1, 2, 2, 5, 5, 4, 3, 5, 3]


2 ) Application of flatMap

μ΄λ²ˆμ—λŠ” μœ„ 2D Array 의 λͺ¨λ“  elements 의 합을 κ΅¬ν•΄λ³΄μž. 일반적으둜 μ΄λŸ¬ν•œ κ΅¬μ‘°μ—μ„œλŠ” 이쀑 For-In Loops λ₯Ό μ‚¬μš©ν•˜κ²Œλœλ‹€.

  • For-In Loops
var sum: Int = 0
for marksArray in marks {
    for element in marksArray {
        sum += element
    }
}
print(sum)  // 52


  • flatMap
let sum = marks
        .flatMap { $0 }
        .reduce(0) { $0 + $1 }

print(sum)  // 52


3 ) Composite case

μ΄λ²ˆμ—λŠ” 2D Collection 에 nil이 ν¬ν•¨λœ 경우λ₯Ό μƒκ°ν•΄λ³΄μž.

let marksWithNil = [[3, nil, 5], [2, 5, nil], [1, 2, 2], [5, 5, 4], [nil, 5, 3]]
  • For-In Loops

일반적으둜 For-In Loops λ₯Ό μ΄μš©ν•˜λ©΄ λ‹€μŒ 방법 쀑 ν•˜λ‚˜λ₯Ό μ‚¬μš©ν•  것이닀.

for marksArray in marksWithNil {
    for element in marksArray {
        anotherSum += element ?? 0
    }
}
print(anotherSum)   // 42
for marksArray in marksWithNil {
    for element in marksArray {
        guard let element = element else { continue }
        anotherSum += element
    }
}
print(anotherSum)   // 42
var anotherSum: Int = 0
for marksArray in marksWithNil {
    for element in marksArray where element != nil {
        anotherSum += element!
    }
}
print(anotherSum)   // 42


  • flatMap

flatMap 을 λ‹€λ₯Έ Higher-order Functions와 ν•¨κ»˜ chaining ν•˜λ©΄ 맀우 κ°„λ‹¨ν•˜κ³  κΉ”λ”ν•œ μ½”λ“œλ‘œ κ΅¬ν˜„ν•  수 μžˆλ‹€.

let anotherSum = marksWithNil
    .flatMap { $0 }
    .reduce(0) { $0 + ($1 ?? 0) }

print(anotherSum)   // 42
let anotherSum = marksWithNil.lazy
        .flatMap { $0 }
        .filter { $0 != nil }
        .reduce(0) { $0 + $1! }

print(anotherSum)   // 42
let anotherSum = marksWithNil.lazy
    .flatMap { $0 }
    .compactMap { $0 }
    .reduce(0) { $0 + $1 }

print(anotherSum)   // 42

5. filter

λ‹€μŒμ€ Swift documentation 의 Instance Method filter(_:)의 μ„€λͺ…이닀.

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

Link: Apple Developer Documentation

filter ν•¨μˆ˜λŠ” Higher-order Functions 쀑 map κ³Ό ν•¨κ»˜ κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” ν•¨μˆ˜λ‹€.
filter λŠ” Collection 의 λͺ¨λ“  elements 에 λ‘œμ§μ„ μˆ˜ν–‰ ν›„ Bool condition 이 true인 값에 λŒ€ν•΄ new Collection을 λ°˜ν™˜ν•œλ‹€.


1 ) Filter some condition

μ•„λž˜ λ°°μ—΄ words μ—μ„œ 문자 oλ₯Ό ν¬ν•¨ν•˜λŠ” elements 만 κ°–λŠ” μƒˆ 배열을 λ§Œλ“€μ–΄λ³΄μž.

let words: [String] = ["room", "home", "train", "green", "heroe"]


  • For-In Loops

Collection 을 μˆœν™˜ν•˜λ©° κ²°κ³Όλ₯Ό 계산해 담을 λ³€μˆ˜λ₯Ό μƒμ„±ν•˜κ³ , For-In Loops λ₯Ό μ΄μš©ν•œλ‹€.

var wordsWithO: [String] = []
for word in words {
    if word.contains("o") {
        wordsWithO.append(word)
    }
}


λ§Œμ•½ κ²°κ³Όλ₯Ό λ³€μˆ˜κ°€ μ•„λ‹Œ μƒμˆ˜μ— λ‹΄κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ? μš°μ„  λ³€μˆ˜ temp λ₯Ό λ§Œλ“€μ–΄ μ €μž₯ν•œλ‹€. 그리고 계산이 λλ‚œ ν›„ 이 λ³€μˆ˜λ₯Ό μƒμˆ˜ wordsWithO 에 ν• λ‹Ήν•˜λ©΄ 값이 λ³΅μ‚¬λœλ‹€.

var temp: [String] = []
for word in words {
    if word.contains("o") {
        temp.append(word)
    }
}

let wordsWithO: [String] = temp


ν•˜μ§€λ§Œ μœ„ 방식은 μ’‹μ•„ 보이지 μ•ŠλŠ”λ‹€. μš°λ¦¬λŠ” 이 문제λ₯Ό closureλ₯Ό μ΄μš©ν•΄ ν•΄κ²°ν•  수 μžˆλ‹€.

let closure = {
    var wordsWithO: [String] = []
    for word in words {
        if word.contains("o") {
            wordsWithO.append(word)
        }
    }
    return wordsWithO
}

let wordsWithO: [String] = closure()

λ§Œμ•½ closure κ°€ μΌνšŒμ„±μœΌλ‘œ μ‚¬μš© ν›„ μ†Œλ©Έλ κ±°λΌλ©΄ λ‹€μŒκ³Ό 같이 anonymous둜 λ§Œλ“€ 수 μžˆλ‹€.

let wordsWithO: [String] = {
    var wordsWithO: [String] = []
    for word in words {
        if word.contains("o") {
            wordsWithO.append(word)
        }
    }
    return wordsWithO
}()


  • filter

ν•˜μ§€λ§Œ filter λ₯Ό μ΄μš©ν•˜λ©΄ μœ„ closure 보닀 더 κ°„κ²°ν•˜κ²Œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.

let wordsWithO: [String] = words.filter { $0.contains("o") }

print(wordsWithO)   // ["room", "home", "heroe"]


2 ) Filter nil

nil 값을 필터링 ν•  λ•ŒλŠ” λ‹€μŒκ³Ό 같은 차이가 μžˆμœΌλ‹ˆ μ£Όμ˜ν•΄μ•Όν•œλ‹€.

μ•„λž˜ λ°°μ—΄ numbersWithNil μ—μ„œ nil이 μ•„λ‹Œ κ°’μ˜ 합을 κ΅¬ν•΄λ³΄μž.

let numbersWithNil = [1, 2, nil, 5, nil, 32, 7]


μš°μ„  numbersWithNil 을 각각 filter, compactMap, map을 μ΄μš©ν•΄ nil을 μ œκ±°ν•΄λ³΄μž.

print(numbersWithNil.filter { $0 != nil })          // [Optional(1), Optional(2), Optional(5), Optional(32), Optional(7)]
print(numbersWithNil.compactMap { $0 })             // [1, 2, 5, 32, 7]
print(numbersWithNil.map { $0 != nil ? $0! : 0 })   // [1, 2, 0, 5, 0, 32, 7]
  • filter λŠ” nil을 μ œκ±°ν•˜μ§€λ§Œ Optional 을 unwrapping ν•˜μ§€λŠ” λͺ» ν•œλ‹€.
  • compactMap 은 nil을 μ œκ±°ν•˜κ³ , Optional 을 unwrappingν•œλ‹€.
  • map 을 μ μ ˆν•œ default value와 ν•¨κ»˜ μ‚¬μš©ν•˜λ©΄, nil을 μ œκ±°ν•˜μ§€λŠ” λͺ» ν•˜μ§€λ§Œ Optional 을 unwrapping ν•  수 μžˆλ‹€.
    (μ΄λ•Œ default valueλŠ” side effectλ₯Ό μΌμœΌν‚€μ§€ μ•Šμ•„μ•Όν•œλ‹€)

사싀 μ˜λ„μ μœΌλ‘œ nil을 νŠΉμ •ν•œ default value둜 λ°”κΎΈλ €λŠ” 것이 μ•„λ‹ˆλΌλ©΄ map case λŠ” 쒋지 λͺ» ν•œ 방법이닀. μœ„ 값을 λ”ν•˜κΈ° μœ„ν•΄ 0μ΄λΌλŠ” 값을 default value 둜 μ£Όμ—ˆμ§€λ§Œ λ§Œμ•½ κ³±ν•˜κΈ°λ‘œ λ³€κ²½λœλ‹€λ©΄? default value λ₯Ό λ‹€μ‹œ 1둜 λ³€κ²½ν•΄μ€˜μ•Όν•œλ‹€.
이런 μ½”λ“œλŠ” μœ μ—°μ„±(μž¬μ‚¬μš©μ„±)이 λ–¨μ–΄μ§ˆ 뿐 μ•„λ‹ˆλΌ λΆˆν•„μš”ν•œ 값을 계속 가지고 λ‹€λ‹ˆλ―€λ‘œ μ„±λŠ₯도 쒋지 λͺ» ν•˜λ‹€. λ˜ν•œ μ½”λ“œκ°€ κΈΈμ–΄μ§€κ±°λ‚˜ μ½”λ“œλ₯Ό μž‘μ„±ν•œ 이후 μœ μ§€λ³΄μˆ˜ ν•  λ•Œ human errorλ₯Ό λ°œμƒμ‹œν‚€λŠ” μš”μΈμ΄ 될 κ°€λŠ₯성도 λ†’λ‹€.

let sumWithFilter = numbersWithNil.lazy
        .filter { $0 != nil }
        .reduce(0) { $0 + $1! }

let sumWithCompactMap = numbersWithNil.lazy
        .compactMap { $0 != nil ? $0 : nil }
        .reduce(0) { $0 + $1 }

let sumWithReduce = numbersWithNil.reduce(0) { $0 + ($1 ?? 0) }

print("sumWithFilter:       \(sumWithFilter)")
print("sumWithCompactMap:   \(sumWithCompactMap)")
print("sumWithReduce:       \(sumWithReduce)")
sumWithFilter:       47
sumWithCompactMap:   47
sumWithReduce:       47


3 ) Compare with TypeScript

TypeScript λŠ” compactMap ν•¨μˆ˜κ°€ μ—†λ‹€. λ”°λΌμ„œ 이런 κ²½μš°λŠ” filter λ₯Ό λ‹€λ₯Έ Higher-order Functions 와 κ²°ν•©ν•΄ λ‹€μŒκ³Ό 같이 κ΅¬ν˜„ν•œλ‹€.

const numbersWithNil: (number | null | undefined)[] = [1, 2, null, 5, undefined, 32, 7]
console.log(numbersWithNil.filter(value => value !== null && value !== undefined))  // [ 1, 2, 5, 32, 7 ]
console.log(numbersWithNil.filter(value => !!value))                                // [ 1, 2, 5, 32, 7 ]
const sumWithFilter = numbersWithNil.filter(value => !!value)
    .reduce((prev, curr) => prev! + curr!, 0)

const sumWithReduce = numbersWithNil.reduce((prev, curr) => prev! + (curr ?? 0), 0)

console.log(`sumWithFilter:     ${sumWithFilter}`)
console.log(`sumWithReduce:     ${sumWithReduce}`)
sumWithFilter:     47
sumWithReduce:     47


λ˜λŠ” β€˜lodash’ 와 같은 Array λ₯Ό 닀루기 μ‰½κ²Œ λ„μ™€μ£ΌλŠ” library λ₯Ό μ‚¬μš©ν•œλ‹€.

import {compact} from 'lodash';

console.log(compact(numbersWithNil))    // [ 1, 2, 5, 32, 7 ]
const sumWithCompact = compact(numbersWithNil)
    .reduce((prev, curr) => prev + curr)

console.log(`sumWithCompact:  ${sumWithCompact}`)
sumWithCompact:    47


필터링 ν•˜λ €λŠ” 값이 nil이고, 이 nil을 버릴거라면 Swift λŠ” compactMap 을 built-in λ©”μ„œλ“œλ‘œ μ œκ³΅ν•˜λ―€λ‘œ μ½”λ“œμ˜ 가독성을 μœ„ν•΄ 쑰건이 μΌμΉ˜ν•  경우 잘 ν™œμš©ν•˜λ„λ‘ ν•˜μž.


4 ) Application of filter

filter λŠ” Primitive Typesλ₯Ό μ €μž₯ν•˜λŠ” Collection 뿐 μ•„λ‹ˆλΌ Class, Structureλ₯Ό μ €μž₯ν•˜λŠ” Collection 에도 μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

struct Tester {
    var name: String
    var age: Int
}

let testers: [Tester] = [Tester(name: "John", age: 23),
                         Tester(name: "Lucy", age: 25),
                         Tester(name: "Tom", age: 32),
                         Tester(name: "Mike", age: 29),
                         Tester(name: "Hellen", age: 19),
                         Tester(name: "Jim", age: 35),
                         Tester(name: "Jamie", age: 30)]


이름이 J둜 μ‹œμž‘ν•˜λ©΄μ„œ λ‚˜μ΄κ°€ 30μ‚΄ 이상인 직원을 μ°ΎλŠ” 예λ₯Ό 보자.

let result: [Tester] = testers.filter { $0.name.prefix(1) == "J" && $0.age >= 30 }

result.forEach { print("\($0.name), \($0.age)") }
Jim, 35
Jamie, 30

6. reduce

λ‹€μŒμ€ Swift documentation 의 Instance Method reduce(_:_:)의 μ„€λͺ…이닀.

func reduce<Result>(
        _ initialResult: Result,
        _ nextPartialResult: (Result, Self.Element) throws -> Result
) rethrows -> Result

Link: Apple Developer Documentation

forEach λ‚˜ map 이 new Collection을 λ°˜ν™˜ν•˜λŠ” 것과 달리 reduce λŠ” λͺ¨λ“  elements λ₯Ό μˆœν™˜ν•˜λ©° 각 elements λ₯Ό κ²°ν•©ν•˜λŠ” λ‘œμ§μ„ 톡해 one resultλ₯Ό λ°˜ν™˜ν•œλ‹€.

μ–Έμ–΄λ‚˜ μ‚¬μ΄νŠΈμ— 따라 Higher-order Functions 의 reduce μ„€λͺ…을 보면

  • (initialResult, Closure[Result, Element])
  • (initialValue, Function[previousValue, currentValue])
  • (initialValue, Function[accumulator, currentValue])

μ΄λŸ°μ‹μœΌλ‘œ parameters 의 이름은 λ‹€λ₯΄μ§€λ§Œ λͺ¨λ‘ λ™μΌν•œ reduce λ₯Ό κ΅¬ν˜„ν•œλ‹€.


μ•„λž˜ λ°°μ—΄μ˜ ν•©κ³Ό 곱을 κ΅¬ν•΄λ³΄μž.

let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


  • For-In Loops
let sum: Int = {
    var sum: Int = 0        // initialValue is '0'
    for number in numbers {
        sum += number
    }
    return sum
}()

let product: Int = {
    var product: Int = 1    // initialValue is '1'
    for number in numbers {
        product *= number
    }
    return product
}()

print("sum: \(sum)   product: \(product)")  // sum: 55   product: 3628800


  • reduce

reduce λ₯Ό μ΄μš©ν•˜λ©΄ λ‹€μŒκ³Ό 같이 맀우 짧고 간단할 뿐 μ•„λ‹ˆλΌ 가독성 λ˜ν•œ μ’‹λ‹€.

let sum: Int = numbers.reduce(0, { (prev, curr) -> Int in prev + curr })    // initialValue is '0'
let product: Int = numbers.reduce(1, {(prev, curr) -> Int in prev * curr }) // initialValue is '1'
let sum: Int = numbers.reduce(0, { $0 + $1 })
let product: Int = numbers.reduce(1) { $0 * $1 }
let sum: Int = numbers.reduce(0, +)
let product: Int = numbers.reduce(1, *)

print("sum: \(sum)   product: \(product)")  // sum: 55   product: 3628800

7. contains

λ‹€μŒμ€ Swift documentation 의 Instance Method contains(_:)와 contains(where:)의 μ„€λͺ…이닀.

func contains(_ element: Self.Element) -> Bool

func contains(where predicate: (Self.Element) throws -> Bool) rethrows -> Bool

Link: Apple Developer Documentation contains(_:)
Link: Apple Developer Documentation contains(where:)

  • contains λŠ” 주어진 쑰건에 λŒ€ν•΄ Bool 자체λ₯Ό λ°˜ν™˜ν•œλ‹€.
    이것은 Collection의 λͺ¨λ“  elementsλ₯Ό Logical OR Operator(||) 둜 μ—°κ²°ν•œ 것과 κ°™λ‹€.
    (condition == element1) || (condition == element2) ||(condition == element3) || …
    λ”°λΌμ„œ ν•˜λ‚˜λΌλ„ trueκ°€ 되면 λ°”λ‘œ μ’…λ£Œ ν›„ trueλ₯Ό λ°˜ν™˜ν•˜κ³ , λ§ˆμ§€λ§‰κΉŒμ§€ false일 경우 falseλ₯Ό λ°˜ν™˜ν•œλ‹€.
  • filter λŠ” 주어진 쑰건에 λŒ€ν•΄ true 인 elements λ₯Ό new Collection으둜 λ°˜ν™˜ν•œλ‹€.


1 ) contains(_:)

contains(_:)λŠ” λ³„λ„μ˜ 쑰건을 κ°–λŠ” 것은 λΆˆκ°€λŠ₯ν•˜κ³  Switch 와 같이 Equal to(==)λ₯Ό κΈ°μ€€μœΌλ‘œ 주어진 쑰건과 μΌμΉ˜ν•˜λŠ”μ§€ μ—¬λΆ€λ§Œ ν™•μΈν•œλ‹€.

μ•„λž˜ 배열이 train 을 ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€ ν™•μΈν•΄λ³΄μž.

let words: [String] = ["room", "home", "train", "green", "heroe"]


  • For-In Loops
let isIncluded: Bool = {
    var isIncluded = false
    for word in words {
        if word == "train" {
            isIncluded = true
            break
        }
    }
    return isIncluded
}()

print(isIncluded)   // true


  • OR Operator(||)
let isIncluded: Bool = words[0] == "train" ||
                       words[1] == "train" ||
                       words[2] == "train" ||
                       words[3] == "train" ||
                       words[4] == "train"

print(isIncluded)   // true


  • Switch
let isIncluded: Bool = {
    switch true {
    case words[0] == "train": return true
    case words[1] == "train": return true
    case words[2] == "train": return true
    case words[3] == "train": return true
    case words[4] == "train": return true
    default: return false
    }
}()

print(isIncluded)   // true
let isIncluded: Bool = {
    switch "train" {
    case words[0], words[1], words[2], words[3], words[4]: return true
    default: return false
    }
}()
print(isIncluded)   // true


  • contains

μœ„μ—μ„œ For-In Loops λ₯Ό μ œμ™Έν•œ λ‹€λ₯Έ 방법듀은 μ½”λ“œμ˜ μ„±λŠ₯κ³Ό μœ μ—°μ„±μ΄ 떨어진닀. κ·Έλ‚˜λ§ˆ κ°€μž₯ 쒋은 방법인 For-In Loops μ—­μ‹œ μ½”λ“œκ°€ κΈΈκ³  가독성이 쒋지 λͺ»ν•˜λ‹€.
κ·Έλ ‡λ‹€λ©΄ contains λŠ” 이λ₯Ό μ–Όλ§ˆλ‚˜ κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„ν•  수 μžˆμ„κΉŒ?

let isIncluded: Bool = words.contains("train")
print(isIncluded)   // true

정말 이게 μ „λΆ€λ‹€!!

λ§Œμ•½ 이것을 ꡳ이 filter 둜 κ΅¬ν˜„ν•œλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?

let isIncluded: Bool = words.filter { $0 == "train" }.count > 0 ? true : false
print(isIncluded)   // true

μ½”λ“œ 가독성도 썩 쒋지 λͺ»ν•  뿐 μ•„λ‹ˆλΌ Collection 의 λͺ¨λ“  elements λ₯Ό λ‹€ λŒμ•„μ„œ new Collection 을 μƒμ„±ν•œ ν›„ 크기λ₯Ό νŒλ‹¨ν•΄μ•Όν•˜λ―€λ‘œ μ„±λŠ₯ λ˜ν•œ 떨어진닀.


2 ) contains(where:)

contains(_:)κ°€ Switch 와 같이 Equal to(==) λ₯Ό λΉ„κ΅ν•˜λŠ” 것과 달리 contains(where:)λŠ” if-statements와 같이 Bool을 νŒλ‹¨ν•  쑰건을 μ œμ‹œν•  수 μžˆλ‹€.

μ΄λ²ˆμ—λŠ” μœ„ 배열이 elements 쀑 문자 eλ₯Ό ν¬ν•¨ν•˜λ©΄μ„œ, 길이가 5 이상인 문자λ₯Ό ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€ ν™•μΈν•΄λ³΄μž.

let isIncluded: Bool = words.contains { $0.contains("e") && $0.count >= 5 }
print(isIncluded)   // true

words λ°”λ‘œ 뒀에 μ˜€λŠ” contains λŠ” contains(where:)이고, closure 내뢀에 μžˆλŠ” contains λŠ” contains(_:)λ‹€.


containsλŠ” Bool`에 λŒ€ν•œ νŒλ‹¨λ§Œ ν•œλ‹€. λ§Œμ•½, 단지 ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ μ—¬λΆ€λ§Œ ν™•μΈν•˜λŠ” 것이 μ•„λ‹ˆλΌ κ·Έ element κ°€ 무엇인지λ₯Ό μ•Œκ³ μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?
μš°λ¦¬λŠ” filter 와 contains λ₯Ό μƒν˜Έ λ³΄μ™„μ μœΌλ‘œ ν•¨κ»˜ μ‚¬μš©ν•¨μœΌλ‘œμ¨ 이λ₯Ό λ©‹μ§€κ²Œ μ²˜λ¦¬ν•  수 μžˆλ‹€.

let wordsWithO = words.filter { $0.contains("o") }
print(wordsWithO)   // ["room", "home", "heroe"]
let wordsWithO = words.filter { $0.contains("o") && $0.count >= 5 }
print(wordsWithO)   // ["heroe"]


3 ) With Dictionaries

let temperatures = ["London": 7, "Athens": 14, "New York": 15, "Cairo": 19, "Sydney": 28]

let hasHighTemperatures = temperatures.contains { $0.value > 25 }
print(hasHighTemperatures)  // true


4 ) With Classes or Structures

class Staff {
    enum Gender {
        case male, female
    }
    
    var name: String
    var gender: Gender
    var age: Int
    
    init(name: String, gender: Gender, age: Int) {
        self.name = name
        self.gender = gender
        self.age = age
    }
}
struct Staff {
    enum Gender {
        case male, female
    }
    
    var name: String
    var gender: Gender
    var age: Int
}


let staff = [Staff(name: "Nick", gender: .male, age: 37),
             Staff(name: "Julia", gender: .female, age: 29),
             Staff(name: "Tom", gender: .male, age: 41),
             Staff(name: "Tony", gender: .male, age: 45),
             Staff(name: "Emily", gender: .female, age: 42),
             Staff(name: "Irene", gender: .female, age: 30)]

let hasStaffOver40 = staff.contains { $0.age > 40 }
print("hasStaffOver40", hasSaffOver40)

let hasMalesOver50 = staff.contains { $0.age > 50 && $0.gender == .male }
print("hasMalesOver50", hasMalesOver50)

let hasFemalesUnder30 = staff.contains { $0.age < 30 && $0.gender == .female }
print("hasFemalesUnder30", hasFemalesUnder30)

8. allSatisfy

λ‹€μŒμ€ Swift documentation 의 Instance Method allSatisfy(_:)의 μ„€λͺ…이닀.

func allSatisfy(_ predicate: (Self.Element) throws -> Bool) rethrows -> Bool

Link: Apple Developer Documentation

  • allSatisfy μ—­μ‹œ contains와 λ§ˆμ°¬κ°€μ§€λ‘œ 주어진 쑰건에 λŒ€ν•΄ Bool 자체λ₯Ό λ°˜ν™˜ν•œλ‹€.
    이것은 Collection의 λͺ¨λ“  elementsλ₯Ό Logical AND Operator(&&) 둜 μ—°κ²°ν•œ 것과 κ°™λ‹€.
    (condition == element1) && (condition == element2) &&(condition == element3) || …
    λ”°λΌμ„œ ν•˜λ‚˜λΌλ„ falseκ°€ 되면 λ°”λ‘œ μ’…λ£Œ ν›„ falseλ₯Ό λ°˜ν™˜ν•˜κ³ , λ§ˆμ§€λ§‰κΉŒμ§€ true일 경우 trueλ₯Ό λ°˜ν™˜ν•œλ‹€.


μ•„λž˜ 배열이 λͺ¨λ‘ 길이가 4 이상인 문자λ₯Ό ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€ ν™•μΈν•΄λ³΄μž.

let words: [String] = ["room", "home", "train", "green", "heroe"]


  • For-In Loops
let isAllTrue: Bool = {
    var isAllTrue = true
    for word in words {
        if word.count < 4 {
            isAllTrue = false
            break
        }
    }
    return isAllTrue
}()

print(isAllTrue)   // true


  • AND Operator(&&)
let isAllTrue: Bool = words[0].count >= 4 &&
        words[1].count >= 4 &&
        words[2].count >= 4 &&
        words[3].count >= 4 &&
        words[4].count >= 4

print(isAllTrue)   // true


  • Switch
let isAllTrue: Bool = {
    switch true {
    case words[0].count < 4: return false
    case words[1].count < 4: return false
    case words[2].count < 4: return false
    case words[3].count < 4: return false
    case words[4].count < 4: return false
    default: return true
    }
}()

print(isAllTrue)   // true


  • allSatisfy

μœ„μ—μ„œ For-In Loops λ₯Ό μ œμ™Έν•œ λ‹€λ₯Έ 방법듀은 μ½”λ“œμ˜ μ„±λŠ₯κ³Ό μœ μ—°μ„±μ΄ 떨어진닀. κ·Έλ‚˜λ§ˆ κ°€μž₯ 쒋은 방법인 For-In Loops μ—­μ‹œ μ½”λ“œκ°€ κΈΈκ³  가독성이 쒋지 λͺ»ν•˜λ‹€.
κ·Έλ ‡λ‹€λ©΄ allSatisfy λŠ” 이λ₯Ό μ–Όλ§ˆλ‚˜ κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„ν•  수 μžˆμ„κΉŒ?

let isAllTrue: Bool = words.allSatisfy { $0.count >= 4 }
print(isAllTrue)   // true

정말 이게 μ „λΆ€λ‹€!!

λ§Œμ•½ 이것을 ꡳ이 filter 둜 κ΅¬ν˜„ν•œλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?

let isAllTrue: Bool = words.filter { $0.count < 4 }.count > 0 ? false : true
print(isAllTrue)   // true

μ½”λ“œ 가독성도 썩 쒋지 λͺ»ν•  뿐 μ•„λ‹ˆλΌ Collection 의 λͺ¨λ“  elements λ₯Ό λ‹€ λŒμ•„μ„œ new Collection 을 μƒμ„±ν•œ ν›„ 크기λ₯Ό νŒλ‹¨ν•΄μ•Όν•˜λ―€λ‘œ μ„±λŠ₯ λ˜ν•œ 떨어진닀.

9. removeAll

λ‹€μŒμ€ Swift documentation 의 Instance Method removeAll(_:)의 μ„€λͺ…이닀.

mutating func removeAll(where shouldBeRemoved: (Self.Element) throws -> Bool) rethrows

Link: Apple Developer Documentation

ν•¨μˆ˜μ˜λ₯Ό 보면 mutating이 λΆ™μ–΄μžˆλ‹€. 즉, remoeveAll(_:) λ©”μ„œλ“œλŠ” new Collection 을 λ°˜ν™˜ν•˜λŠ” 것이 μ•„λ‹ˆλΌ original Collection을 μˆ˜μ •ν•œλ‹€.


1 ) Compare with filter

κ°€μž₯ κ°„λ‹¨ν•œ ν˜•νƒœλ₯Ό 톡해 filter 와 removeAll 의 차이λ₯Ό μ•Œμ•„λ³Έλ‹€.

  • filter
let scores: [Int] = [100, 75, 80, 66, 93, 52, 96, 87, 72]
let graterThanOrEqualNinety: [Int] = scores.filter { $0 >= 90 }
print(graterThanOrEqualNinety)  // [100, 93, 96]


  • removeAll
scores.removeAll { $0 >= 90 }   // Cannot use mutating member on immutable value: 'scores' is a 'let' constant
print(scores)


removeAll 이 mutating이기 λ•Œλ¬Έμ— 배열을 var둜 μ„ μ–Έν•΄μ•Όν•œλ‹€.

var scores: [Int] = [100, 75, 80, 66, 93, 52, 96, 87, 72]
scores.removeAll { $0 >= 90 }
print(scores)   // [75, 80, 66, 52, 87, 72]


μœ„ filter 와 λ™μΌν•œ κ²°κ³Όλ₯Ό λ§Œλ“€λ €λ©΄ 쑰건식에 !을 μ·¨ν•΄ Bool κ²°κ³Όλ₯Ό 뒀집어주면 λœλ‹€.

scores.removeAll { $0 < 90 }
print(scores)   // [100, 93, 96]
let graterThanOrEqualNinety = scores.removeAll { $0 < 90 }
print(graterThanOrEqualNinety)  // ()

Closure 의 쑰건식이 λ™μΌν•˜λ‹€λ©΄ removeAll 은 filter 의 λ°˜λŒ€μ˜ κ²°κ³Όλ₯Ό κ°–λŠ”λ‹€. λ˜ν•œ original Collection 은 κ·ΈλŒ€λ‘œ 두고 new Collection 을 λ°˜ν™˜ν•˜λŠ” filter 와 달리 removeAll 은 original Collection 을 λ³€κ²½ν•œλ‹€.

removeAll 의 Return Type 은 VoidλΌλŠ” νƒ€μž…μ˜ νŠΉμˆ˜ν•œ 값을 λ°˜ν™˜ν•œλ‹€. 이 값은 ()둜 쓰여진 Empty Tuple이닀.


2 ) Compare with filter(_:) & contains(_:)

var words: [String] = ["room", "home", "train", "green", "heroe"]


  • filter
let wordsWithO: [String] = words.filter { $0.contains("o") }
print(wordsWithO)   // ["room", "home", "heroe"]
  • removeAll
words.removeAll { $0.contains("o") }
print(words)        // ["train", "green"]


3 ) Compare with filter(_:) & contains(where:)

var words: [String] = ["room", "home", "train", "green", "heroe"]


  • filter
let wordsWithO = words.filter { $0.contains("o") && $0.count >= 5 }
print(wordsWithO)   // ["heroe"]
  • removeAll
words.removeAll { $0.contains("o") && $0.count >= 5 }
print(words)        // ["room", "home", "train", "green"]

19. sort, sorted

λ‹€μŒμ€ Swift documentation 의 Instance Method sort(by:)와 sorted(by:)의 μ„€λͺ…이닀.

mutating func sort(by areInIncreasingOrder: (Self.Element, Self.Element) throws -> Bool) rethrows

func sorted(by areInIncreasingOrder: (Self.Element, Self.Element) throws -> Bool) rethrows -> [Self.Element]

Link: Apple Developer Documentation sort(by:)
Link: Apple Developer Documentation sorted(by:)

Swift 의 Higher-order Functions 의 μ •λ ¬μ—λŠ” 2가지가 μžˆλ‹€.

  • sort: Closure 의 쑰건에 따라 original Collection 을 μ •λ ¬ν•œλ‹€.
  • sorted: Closure 의 쑰건에 따라 μ •λ ¬ ν›„ new Collection 을 λ°˜ν™˜ν•œλ‹€.


1 ) Ascending Order

μ•„λž˜ 배열을 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•΄λ³΄μž.

var numbers: [Int] = [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]


  • For-In Loops
var swap: Bool = false

repeat {
    swap = false
    
    for i in 0..<numbers.count - 1 {
        if numbers[i] > numbers[i + 1] {
            let temp = numbers[i + 1]
            numbers[i + 1] = numbers[i]
            numbers[i] = temp
            swap = true
        }
    }
    
} while swap

print(numbers)  // [2, 5, 6, 8, 9, 15, 24, 32, 42, 74, 87]


  • sort

sort λ₯Ό μ΄μš©ν•˜λ©΄ λ‹€μŒκ³Ό 같이 μ‰½κ²Œ 정렬이 κ°€λŠ₯ν•˜λ‹€.

var numbers: [Int] = [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]
numbers.sort { $0 < $1 }

λŒ€λΆ€λΆ„μ˜ κ²½μš°λŠ” Trailing Closures λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 κΉ”λ”ν•œ μ½”λ“œλ₯Ό μ œκ³΅ν•˜μ§€λ§Œ μ •λ ¬ ν•¨μˆ˜λŠ” Shorthand Argument Names λ§ˆμ €λ„ μƒλž΅ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— λ‹€μŒκ³Ό 같이 μž‘μ„±ν•˜λ©΄ μ½”λ“œλ₯Ό 더 쀄일 수 μžˆλ‹€.

numbers.sort(by: <)

λ§ˆμ§€λ§‰μœΌλ‘œ sort(by:)의 κΈ°λ³Έ λ™μž‘μ€ Ascending Order이기 λ•Œλ¬Έμ— 이 λ§ˆμ €λ„ μƒλž΅ κ°€λŠ₯ν•˜λ‹€.

numbers.sort()
print(numbers)  // [2, 5, 6, 8, 9, 15, 24, 32, 42, 74, 87]


  • sorted

λ•Œλ‘œλŠ” 원본 배열을 μˆ˜μ •ν•˜μ§€ μ•Šκ³  μ •λ ¬λœ μƒˆ 배열이 ν•„μš”ν•  수 μžˆλ‹€. sort(by:)λŠ” mutating이기 λ•Œλ¬Έμ— 이λ₯Ό μœ„ν•΄μ„œ sort(by:)λ₯Ό ν•˜κΈ° μ „ 배열을 미리 λ³΅μ‚¬ν•΄μ•Όν•œλ‹€. λ˜λŠ” 원본 배열이 let으둜 μ„ μ–Έλ˜μ–΄ λ³€κ²½ν•  수 없을 μˆ˜λ„ μžˆλ‹€. μ΄λ•Œ μ‚¬μš©ν•˜λ©΄ 쒋은 ν•¨μˆ˜κ°€ sorted(by:)λ‹€.

var numbers: [Int] = [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]
let ascendingOrdered = numbers.sorted { $0 < $1 }
print(ascendingOrdered) // [2, 5, 6, 8, 9, 15, 24, 32, 42, 74, 87]
print(numbers)          // [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]

sorted(by:) μ—­μ‹œ Trailing Closures λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 더 κΉ”λ”ν•œ μ½”λ“œ μž‘μ„±μ΄ κ°€λŠ₯ν•˜λ‹€.

let ascendingOrdered = numbers.sorted(by: <)

λ§ˆμ°¬κ°€μ§€λ‘œ by: < λ§ˆμ €λ„ μƒλž΅ κ°€λŠ₯ν•˜λ‹€.

let ascendingOrdered = numbers.sorted()
print(ascendingOrdered) // [2, 5, 6, 8, 9, 15, 24, 32, 42, 74, 87]


2 ) Descending Order

  • sort
var numbers: [Int] = [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]
numbers.sort(by: >)
print(numbers)  // [87, 74, 42, 32, 24, 15, 9, 8, 6, 5, 2]
  • sorted
var numbers: [Int] = [5, 87, 2, 6, 15, 24, 8, 42, 74, 9, 32]
let descendingOrdered = numbers.sorted(by: >)
print(descendingOrdered)    // [87, 74, 42, 32, 24, 15, 9, 8, 6, 5, 2]

11. split

λ‹€μŒμ€ Swift documentation 의 Instance Method compactMap(_:)의 μ„€λͺ…이닀.

func split(
    separator: Self.Element,
    maxSplits: Int = Int.max,
    omittingEmptySubsequences: Bool = true
) -> [Self.SubSequence]
func split(
    maxSplits: Int = Int.max,
    omittingEmptySubsequences: Bool = true,
    whereSeparator isSeparator: (Self.Element) throws -> Bool
) rethrows -> [Self.SubSequence]

Link: Apple Developer Documentation
Link: Apple Developer Documentation

split 은 주어진 쑰건에 따라 λ‚˜λˆ  자기 μžμ‹ μ˜ SubSequence νƒ€μž…μ˜ new Collection을 λ°˜ν™˜ν•œλ‹€.

예λ₯Ό λ“€μ–΄ String을 split ν•˜λ©΄ String.SubSequence인 Substring νƒ€μž…μ˜ new Collection
즉, Array<Substring> (= [Substring])을 λ°˜ν™˜ν•œλ‹€.

Substring 은 λ‹€μŒ 링크λ₯Ό μ°Έκ³ ν•œλ‹€. About Substrings


  • split(separator:maxSplits:omittingEmptySubsequences:)
let line = "BLANCHE:   I don't want realism. I want magic!"


split 에 μ˜ν•΄ " "λ₯Ό κΈ°μ€€μœΌλ‘œ λ‚˜λ‰˜μ–΄ 생긴 Substring이 Collection Array에 λ‹΄κ²Όλ‹€.

print(type(of: line))       // String

let splited = line.split(separator: " ")
print(splited)          // ["BLANCHE:", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]
print(type(of: splited))    // Array<Substring>


maxSplitsλ₯Ό 톡해 λͺ‡ 번 split 을 ν•  것인지 μ •ν•  수 μžˆλ‹€.

let splitedMaxOne = line.split(separator: " ", maxSplits: 1)
print(splitedMaxOne)    // ["BLANCHE:", "  I don\'t want realism. I want magic!"]


split 은 기본적으둜 White-Spaceλ₯Ό λ¬΄μ‹œν•œλ‹€. λ”°λΌμ„œ 이λ₯Ό λ¬΄μ‹œν•˜μ§€ μ•ŠμœΌλ €λ©΄ omittingEmptySubsequences의 argument 둜 falseλ₯Ό μ „λ‹¬ν•œλ‹€.

print(line.split(separator: " ", omittingEmptySubsequences: true))  // default
// ["BLANCHE:", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]

print(line.split(separator: " ", omittingEmptySubsequences: false))
// ["BLANCHE:", "", "", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]


  • split(maxSplits:omittingEmptySubsequences:whereSeparator:)

μœ„ split κ³Ό λ™μΌν•œ 역할을 ν•˜λŠ” split 이닀. λ‹€λ§Œ, μœ„ split 은 λ‚˜λˆ„λŠ” 기쀀을 μ§€μ •ν•˜λŠ” separatorκ°€ 첫 번째 argument μ˜€μœΌλ‚˜ 이 split ν•¨μˆ˜λŠ” λ§ˆμ§€λ§‰ whereSeparatorκ°€ λ§ˆμ§€λ§‰ argument둜 μ˜¨λ‹€.

즉, separatorλ₯Ό Trailing Closure 둜 μž‘μ„±ν•˜κΈ° μœ„ν•΄ μ œκ³΅λ˜λŠ” λ‹€λ₯Έ ν˜•νƒœμ˜ λ™μΌν•œ split ν•¨μˆ˜λ‹€.

let line = "BLANCHE:   I don't want realism. I want magic!"
let splited = line.split { $0 == " " }
print(splited)          // ["BLANCHE:", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]
print(type(of: splited))    // Array<Substring>




Reference

  1. β€œFirst-class citizen.” Wikipedia. Oct. 15, 2022, Wikipedia - First Class Citizen.
  2. β€œFirst-class function.” Wikipedia. Jul. 14, 2022, Wikipedia - First Class Function.
  3. β€œHigher-order function.” Wikipedia. Sep. 8, 2022, Wikipedia - Higher-Order Function.
  4. β€œNon-local variable.” Wikipedia. May. 12, 2022, Wikipedia - Non-local Variable.
  5. β€œHigher-Order Functions in Swift.” Medium, Jun. 9, 2020, Higher-Order Functions in Swift.
  6. β€œUnderstanding Higher Order Functions in Swift.” APPCODA, Feb. 26, 2020, Understanding Higher Order Functions in Swift.
  7. β€œHigher Order Functions in Swift.” Level Up Coding, Aug. 12, 2020, Level Up Coding - Higher Order Functions in Swift.