Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
SwiftUI/SwiftUI(Intermediate)

1. sorted / filter / map / compactMap

by Toughie 2023. 5. 21.

⭐️sorted / filter / map / compactMap⭐️

고차함수를 통해 배열을 좀 더 효율적으로 활용하는 방법을 알아보자.

 

먼저 예제를 위한 코드

 

MODEL

//  Created by Toughie on 2023/05/20.
//

import SwiftUI

// MARK: MODEL
struct UserModel: Identifiable {
    let id = UUID().uuidString
    let name: String
    let point: Int
    let isVarified: Bool
}

VIEWMODEL

// MARK: VIEWMODEL

final class ArrayModificationViewModel: ObservableObject {
    
    @Published var dataArray: [UserModel] = []
    @Published var sortedArray: [UserModel] = []
    @Published var filteredArray: [UserModel] = []
    @Published var mappedArray: [String] = []
    
    
    init() {
        getDummies()
        sortDataArray()
        filterDataArray()
        mapDataArray()
    }
    
    private func sortDataArray() {
//        filteredArray = dataArray.sorted { user1, user2 in
//            return user1.point > user2.point
//        }
        sortedArray = dataArray.sorted { $0.point > $1.point}
    }
    
    private func filterDataArray() {
//        filteredArray = dataArray.filter({ user in
//            return !user.isVarified
////            return user.name.contains("F")
//        })
        
        filteredArray = dataArray.filter { $0.isVarified }
    }
    
    private func mapDataArray() {
//        mappedArray = dataArray.map({ (user) -> String in
//            return user.name
//
//        })
        mappedArray = dataArray.map { $0.name }
    }
    
    private func getDummies() {
        let user1 = UserModel(name: "Toughie", point: 70, isVarified: false)
        let user2 = UserModel(name: "Oner", point: 83, isVarified: true)
        let user3 = UserModel(name: "Keria", point: 87, isVarified: true)
        let user4 = UserModel(name: "Zeus", point: 82, isVarified: true)
        let user5 = UserModel(name: "Faker", point: 99, isVarified: true)
        let user6 = UserModel(name: "Gumayusi", point: 85, isVarified: true)
        let user7 = UserModel(name: "Wolf", point: 90, isVarified: false)
        let user8 = UserModel(name: "Bang", point: 85, isVarified: false)
        let user9 = UserModel(name: "Canyon", point: 81, isVarified: false)
        let user10 = UserModel(name: "Ruler", point: 87, isVarified: true)
        
        self.dataArray.append(contentsOf: [
        user1, user2, user3, user4, user5, user6, user7, user8, user9, user10
        ])
    }
}

(예시의 코드와 같이 테스트 더미를 추가할 때는 함수형태로 구현해 두는 것이 깔끔하다.)

 

VIEW

// MARK: VIEW
struct SortFilterMap: View {
    let backgroundColor = #colorLiteral(red: 0.6814126372, green: 0.8912058473, blue: 1, alpha: 1)
    
    @StateObject var viewModel: ArrayModificationViewModel = ArrayModificationViewModel()
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                //매핑된 이름만 표시해 보기 위한 코드
//                ForEach(viewModel.mappedArray, id: \.self) { name in
//                    Text(name)
//                        .font(.title)
//                }

                //( viewModel.sortedArray ...)
                ForEach(viewModel.filteredArray) { user in
                    VStack(alignment: .leading) {
                        Text(user.name)
                            .font(.title3)
                        HStack {
                            Text("Points: \(user.point)")
                            Spacer()
                            if user.isVarified {
                                Image(systemName: "checkmark.shield.fill")
                                    .foregroundColor(Color.green)
                            }
                        }
                    }
                    .padding()
                    .background(Color(backgroundColor).cornerRadius(10))
                }
            }
        }
        .padding()
        .ignoresSafeArea(edges: .bottom)
    }
}

 

Sorted(by:)

요소끼리 비교해서 정렬된 요소 배열을 반환하는 메서드.

현재 dataArray는 [UserModel] 타입이다. 여기서 원하는 조건에 따라 요소들을 정렬할 수 있다.

ex. 점수의 오름차순/내림차순

    private func sortDataArray() {
//        filteredArray = dataArray.sorted { user1, user2 in
//            return user1.point > user2.point
//        }
        sortedArray = dataArray.sorted { $0.point > $1.point}
    }

보통 클로저 문법 최적화를 통해 아래 코드와 같이 많이 사용한다.

위 주석 코드와 같은 코드인데, 여기서 user1과 user2는 더미데이터의 user1/user2가 아니라
배열 내 요소를 지칭하는 것이다. 

 

즉 user1($0)이 첫번째 인자이고, user2($1)이 두번째 인자인데,

첫번째 인자는 정렬 할 요소이고, 두번째 인자는 정렬의 기준이 되는 요소이다.

-> 배열 내 요소를 하나씩 비교해서 정렬하는 것.

 

배열의 크기가 클수록 정렬 속도는 일반적으로 느려질 수 있다.

* .sorted(by:)는  평균 시간 복잡도가 O(n log n)을 가지는 퀵소트(Quicksort) 알고리즘을 사용함

다만 Swift의 내장 정렬 알고리즘 최적화 덕분에 웬만하면 괜찮은 성능을 보장받을 수 있다고 한다.

 

filter(_:)

말 그대로 필터링 하는 메서드. (원하는 조건에 부합하는 요소들만 담긴 배열을 반환한다.)

    private func filterDataArray() {
//        filteredArray = dataArray.filter({ user in
//            return user.isVarified
////            return user.name.contains("F")
//        })
        
        filteredArray = dataArray.filter { $0.isVarified }
    }

마찬가지로 아래 코드와 같이 클로저 최적화 된 형태로 사용을 많이한다.

배열의 isVarified가 true인 요소들만 담은 배열을 반환한다.

이름에 특정 문자를 포함하는 요소들만 반환 할 수도 있고 필요에 따라 다양한 조건을 걸 수 있다.

 

Map(_:)

map은 배열의 요소를 클로저 내부 매핑 조건에 따라 변환하는 메서드이다.

이 과정에서 요소의 타입이 변환되고, 이를 매핑(mapping)이라고 한다.

매핑은 요소의 타입을 변환하는데 사용될 뿐만 아니라, 특정 객체의 프로퍼티를 추출해서

새로운 배열로 매핑하는 것도 가능하다.(이번 예시)

 

(Declaration을 보면 알 수 있듯 기존 배열(transform)의 요소가 제네릭 타입(T)으로 매핑 되고, 이것이 담긴 [T]가 반환된다.)

 

    private func mapDataArray() {
//        mappedArray = dataArray.map({ (user) -> String in
//            return user.name
//
//        })
        mappedArray = dataArray.map { $0.name }
    }

UserModel 객체에서 name 프로퍼티만 추출해서 [String] 배열로 매핑

 

compactMap

위의 매핑 예시를 보면 (user) -> String 타입이라 String을 반환해야 하는데,
만약 모델에서 name이 String?이면 어떻게 할까?

 

struct UserModel: Identifiable {
    let id = UUID().uuidString
    //Optional String
    let name: String?
    let point: Int
    let isVarified: Bool
}
    private func mapDataArray() {
        mappedArray = dataArray.map({ (user) -> String in
            return user.name ?? "NIL🔺"

        })
//        mappedArray = dataArray.map { $0.name }
    }

이렇게 nil coalescing을 이용해서 nil을 처리할 수도 있지만..

 

 

compactMap을 통해서 nil인 녀석들을 제외할 수도 있다.

기존 배열(transform)의 요소에서 변환 과정(매핑)을 거쳐서 nil이 아닌 요소들을 가지고 새로운 배열을 반환하는 메서드이다.

    private func compactMapArray() {

//        compactMappedArray = dataArray.compactMap({ (user) -> String? in
//            return user.name
//        })
//
        compactMappedArray = dataArray.compactMap { $0.name }
    }

 

이번 내용들을 합쳐서 아래와 같이 한 번에 적용할 수도 있다.

    private func complexArray() {
//        let sort = dataArray.sorted { $0.point > $1.point}
//        let filter = dataArray.filter { $0.isVarified }
//        let map = dataArray.compactMap { $0.name }
//
        mappedArray = dataArray
                        .sorted { $0.point > $1.point}
                        .filter { $0.isVarified }
                        .compactMap { $0.name }
    }

1 - 점수를 기준으로 내림차순 정렬

2 - isVarified가 true인 경우만 필터링

3 - 1,2를 만족하며 이름이 있는 경우, 이름만 추출해서 배열 반환.

 

이와 같이 sorted, filter, map을 활용하면 원하는 데이터를 적절하게 추출/변형할 수 있을 것이다 :)