Danny의 iOS 컨닝페이퍼
article thumbnail

[TIL #26] 2023 / 06 / 07

오늘은 정렬하는 방법에 대해서 간단히 알아보겠습니다.

 

 

sort와 sorted

둘 다 정렬하는 메서드이지만 약간의 차이점이 있습니다. 각각 상황에 맞게 사용하면 됩니다.

 

바로 간단히 예시를 보면서 알아봅시다.

 

sort

원본 배열을 정렬하여 변경합니다. 원본 배열 자체를 수정하는 것이므로 리턴값이 없습니다.

// 1~10까지 랜덤 숫자 배열을 만들었습니다.
var randomNum = Array(1...10).shuffled()
print(randomNum)
// [10, 3, 2, 7, 1, 4, 9, 5, 6, 8]
// 원본 배열이 변경되도 상관없을 때
randomNum.sort()
print(randomNum)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

sorted

정렬된 새로운 배열로 반환을 합니다. 원본 배열은 변하지 않고 새로운 배열을 리턴합니다.

(복사해서 새로 만들어 주므로, 많은 값을 정렬하면 메모리 효율이 떨어질 수도...)

// 1~10까지 랜덤 숫자 배열을 만들었습니다.
var randomNum = Array(1...10).shuffled()
print(randomNum)
// [10, 3, 2, 7, 1, 4, 9, 5, 6, 8]
// 원본 배열을 수정하면 안 될 때, 새로운 변수를 만들어 사용
let sortNum = randomNum.sorted()
print(sortNum)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

 

오름차순, 내림차순

간단하게 오름차순과 내림차순을 하는 방법들을 알아봅시다.

 

일단 sort, sorted(by:) 메서드가 가장 편합니다.

var randomNum = Array(1...10).shuffled()

let ascending = randomNum.sorted { lhs, rhs in
    // 두 값을 비교해서 작으면 값을 리턴, 즉 오름차순
    return lhs < rhs
}
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

그럼 반대로 해주면 내림차순이 되겠죠?

let descending = randomNum.sorted { lhs, rhs in
    // 두 값을 비교해서 크면 값을 리턴, 즉 내림차순
    return lhs > rhs
}
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

 

조금 더 간결히 사용하려면 클로저의 인자($0, $1)로 접근해서 사용도 가능하죠

let ascending = randomNum.sorted { $0 < $1 }
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let descending = randomNum.sorted { $0 > $1 }
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

 

부등호만 사용하여 더 간결하게 만들 수도 있어요.

Operator method라고 static 메서드로 연산자들이 구현이 돼있어서 이렇게도 사용이 가능합니다.

let ascending = randomNum.sorted(by: <)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let descending = randomNum.sorted(by: >)
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

 

 

구조체, 클래스의 정렬

먼저 Person을 구조체로 만들어줬습니다.

struct Person {
    let name: String
    let age: Int
}

var persons = [Person(name: "A", age: 60),
               Person(name: "D", age: 20),
               Person(name: "B", age: 50),
               Person(name: "E", age: 30),
               Person(name: "C", age: 10)
]

 

 

방법 1 - 기본적인 sort, sorted(by:) 메서드 사용

 

이 방법이 가장 간단한 방법인 것 같습니다.

위에서 사용한 방식과 동일하게 sort의 클로저 내부를 정의해 주면 됩니다.

let sortPersons = persons.sorted { $0.name < $1.name }
// name "A", age 50
// name "B", age 60
// name "C", age 10
// name "D", age 20
// name "E", age 30

let sortPersons = persons.sorted { $0.age < $1.age }
// name "C", age 10
// name "D", age 20
// name "E", age 30
// name "A", age 50
// name "B", age 60

 

 

방법 2 - KeyPathComparator를 사용

 

iOS 15+ 부터 사용이 가능하고 KeyPath로 항목에 접근하여 정렬을 시켜주는 방법입니다.

 

먼저 나이를 기준으로 정렬을 시키면,

// keyPath로 비교할 대상에 접근하여 정렬을 합니다.
// order의 기본값은 forward이고 오름차순으로 정렬해 주네요. reverse는 내림차순
let keyPathComparator = KeyPathComparator(\Person.age,
                                           order: .forward)

let sortPersons = persons.sorted(using: keyPathComparator)

// 나이를 기준으로 오름차순으로 정렬
// name "C", age 10
// name "D", age 20
// name "E", age 30
// name "A", age 50
// name "B", age 60

 

마찬가지로 문자열도 비교가 가능합니다.

(여긴 내림차순으로 해봅시다)

// comparator에서 localized와 localizedStandard의 차이를 모르겠지만,그냥 localizedStandard를 쓰는걸로...
let keyPathComparator = KeyPathComparator(\Person.name,
                                           comparator: .localizedStandard,
                                           order: .reverse)

let sortPersons = persons.sorted(using: keyPathComparator)

// 이름을 기준으로 내림차순으로 정렬
// name "E", age 30
// name "D", age 20
// name "C", age 10
// name "B", age 60
// name "A", age 50

 

 

방법 3 - 프로토콜 Comparable를 채택

 

값을 비교하기 위해 프로토콜 Comparable을 채택하여 사용하는 방법입니다.

 

일단 Comparable를 채택하고 비교를 위해 필수로 연산자 메서드를 구현해 주면 됩니다.

(class의 경우 '=='와 '<' 메서드를 필수로 구현해줘야 합니다.)

struct Person: Comparable {
    let name: String
    let age: Int
    
    // 나이를 기준으로 오름차순으로 정렬
    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
}

let sortPersons = persons.sorted()

// name "C", age 10
// name "D", age 20
// name "E", age 30
// name "A", age 50
// name "B", age 60

 

 

방법 4 - SortDescriptor를 사용

 

KeyPathComparator와 마찬가지로 iOS 15+ 부터 사용이 가능합니다.

 

자세히는 모르겠지만 NSObject를 사용해서 그런가 런타임시에만 동작이 가능하다고 하네요...

 

사용방법을 간단히 보면,

일단 NSObject를 채택하고 Objective-C와 호환성을 위해 어노테이션 @objc를 사용해서 모델을 만들어 줘야 합니다.

 

그리고 KeyPathComparator와 비슷하게 사용하면 됩니다.

class Person: NSObject {
    @objc let name: String
    @objc let age: Int
    
    // NSObject를 채택 했으니 super를 불러줘야 합니다
    init(name: String, age: Int) {
        self.name = name
        self.age = age
        super.init()
    }
}

let sortDescriptor = SortDescriptor(\Person.age,
                                     order: .forward)

let sortPersons = persons.sorted(using: sortDescriptor)

// name "C", age 10
// name "D", age 20
// name "E", age 30
// name "A", age 50
// name "B", age 60

 

이렇게 직접 Objective-C 언어와도 호환이 가능하다고 합니다.

let sortDescriptor = NSSortDescriptor(key: #keyPath(Person.age), ascending: true)
let sortPersons = (persons as NSArray).sortedArray(using: [sortDescriptor]) as! [Person]

 

 

여러 조건 정렬하기

사람의 이름, 나이, 성별, 자산의 정보가 있습니다. 

 

결혼 매칭 서비스를 만든다고 생각을 해봅시다 

 

마음이 아프게도 위의 정보들에 따라서 신랑, 신붓감 순위가 매겨질 거예요.

 

첫 번째로 일단 매칭 서비스니 구분을 위해 성별을 구분해줘야 합니다.

둘째로 재력이 있어야 되고, 셋째로 나이가 어리면 좋은 점수를 받을 수 있겠죠?!

 

이러면 바로 매칭 서비스를 시작할 수 있을 있겠죠. 한번 이렇게 정렬하여 만들어 봅시다.

struct Person {
    let name: String
    let age: Int
    let isMale: Bool
    let asset: Int
}

let persons = [Person(name: "여자-1호", age: 25, isMale: false, asset: 4000),
               Person(name: "남자-1호", age: 33, isMale: true, asset: 8000),
               Person(name: "여자-2호", age: 33, isMale: false, asset: 7000),
               Person(name: "남자-2호", age: 38, isMale: true, asset: 6000),
               Person(name: "여자-3호", age: 28, isMale: false, asset: 4000),
               Person(name: "남자-3호", age: 28, isMale: true, asset: 6000)]

 

위의 조건대로 정렬을 해보면

let ranking = persons.sorted {
    if $0.isMale != $1.isMale {
        // 먼저 남자일 때 위로, 여자는 아래로 내리겠습니다.
        return $0.isMale
        
    } else if $0.asset != $1.asset {
        // 자산이 많은 순으로 정렬합니다. (내림차순)
        return $0.asset > $1.asset
    } else {
        // 자산 동일하거면 나이가 어린순으로 정렬 (오름차순)
        return $0.age < $1.age
    }
}

// Person(name: "남자-1호", age: 33, isMale: true, asset: 8000)
// Person(name: "남자-3호", age: 28, isMale: true, asset: 6000)
// Person(name: "남자-2호", age: 38, isMale: true, asset: 6000)
// Person(name: "여자-2호", age: 33, isMale: false, asset: 7000)
// Person(name: "여자-1호", age: 25, isMale: false, asset: 4000)
// Person(name: "여자-3호", age: 28, isMale: false, asset: 4000)

 

'남자-1호'와 '여자-2호'가 자산이 가장 많으므로, 가장 높은 순위로 나타나네요.

그리고 나머지도 조건인 자산이 같으면 어린순으로 정렬이 잘되는 걸 확인할 수 있습니다.

 

보기엔 살짝 복잡해 보일 수 도 있지만, 한 번만 해보면 의외로 간단하네요 ㅎ

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!