예거's Bicycle for the mind

[Swift] Nested Array (2차원 배열)에서 튜플을 이용해 다중 정렬하는 법 (sort, tuple, Closure) 본문

iOS & Swift

[Swift] Nested Array (2차원 배열)에서 튜플을 이용해 다중 정렬하는 법 (sort, tuple, Closure)

유예거 2021. 9. 30. 18:53

스위프트 Array(배열)은 순서가 있는 Ordered Collection Type 이다.

Array를 2중으로 만들어서 정렬(sort)을 공부해보자.

 

먼저, sort()sorted() 정렬 메소드의 차이를 알아야 한다.

sort 는 원본값에 영향을 주고 리턴값이 없다.

sorted 는 원본값에 영향을 주지 않고, 새로운 리턴값을 반환한다.

let 으로 선언한 배열이 있다면, sort 메소드를 사용할 수 없다.

 

 

let 으로 선언한 배열은 'immutable value' 가 된다. 즉, 변경이 불가능한 값! 따라서 원본을 건드리는 sort() 메소드를 쓸 수 없다.

만약 원본이 변경되길 원하지 않는 배열이 있다면, let 으로 선언한 뒤 sorted() 메소드로 새로운 값을 만들어 쓰는 게 좋다.

 

배열 예시를 만들고 sorted() 메소드를 붙여서 간단하게 정렬해보자.

 

// 정렬 실험을 위한 배열 예시 생성
let subjects = ["나", "바", "다", "가", "라", "마"]
let grades = ["C", "A", "B"]

subjects.sorted()
// ["가", "나", "다", "라", "마", "바"] -> 가나다 정렬됨

grades.sorted()
// ["A", "B", "C"] -> ABC 정렬됨

 

정렬에 아무 조건을 걸지 않는다면

한글 String 에서는 가나다 순서대로, 영문 String 에서는 알파벳 순서대로 '오름차순' 정렬된다.

 

그러면 2차원 배열인 [[String]] 예시를 만들어보자. 여러 번 중첩된 배열은 Nested Array 라고도 부른다.

Nest 는 새의 둥지를 말하는데, Nested 는 "중첩된" 이란 의미가 된다.

 

그 의미를 이해하기 위해선 아래 이미지를 참고해보자. 새둥지는 여러 겹의 나뭇가지로 만들어져 있다.

여러 겹으로 둘러 쌓인 모습을 Nested 라고 생각하면 쉽다.

 

새둥지(Nest) 모양

 

Nested 는 Array 뿐만 아니라, 어떤 단어에도 붙을 수 있다.

실제로 스위프트 문법에서도 얼마든지 Nested Types(중첩 타입), Nested Functions(중첩 함수), Nested Enums(중첩 열거형) 등을 만들어낼 수 있다.

 

다시 본론으로 돌아와서, 중첩 배열인 [[String]] 을 만들어서 갖고 놀아보자.

 

// [[String]] 생성!

// 가나다라마바 라는 과목명과 학점 ABC를 배열로 묶어서 다시 배열에 넣은 형태다.
let myGrades = [
    ["B", "다"],
    ["A", "바"],
    ["C", "나"],
    ["B", "가"],
    ["A", "라"],
    ["A", "마"]
]

myGrades.sorted()
// 컴파일 에러!
// -> '[String]'이 'Comparable' 프로토콜을 준수하지 않기에 정렬할 수 없음

 

일부로 가나다라마바와 ABC 순서가 엉망으로 섞이도록 만들었다.

let 으로 선언한 myGrades  에다가 sorted() 메소드를 적용해보면 컴파일 에러가 뜬다.

 

왜? String 타입은 Comparable 프로토콜을 준수하지만, String 이 배열 안에 들어간 [String] 은 그 프로토콜을 준수하지 않기 때문이다.

 

프로토콜 Comparable
: A type that can be compared using the relational operators <, <=, >=, and >.

 

그렇다면 [String]을 파고 들어서 정렬 조건을 걸어보자.

클로저와 단축인자이름($)을 사용해보자.

 

// 후행 클로저(Trailing Closure)와 단축인자이름(Shorthand Argument Names) 사용

print(myGrades.sorted { $0[0] < $1[0] })
// [["A", "바"], ["A", "라"], ["A", "마"], ["B", "다"], ["B", "가"], ["C", "나"]]

// 또는 아래와 같은 문법도 모두 같은 결과를 낸다.
print(myGrades.sorted(by:) { $0[0] > $1[0] })
print(myGrades.sorted(by: { $0[0] > $1[0] }))

 

ABC 순서대로 정렬됐지만, 바로 뒤에 붙는 바라마다가나 가 엉망이다.

ABC 정렬을 1순위 정렬로 하고, 그 다음엔 2순위 정렬로 가나다 정렬을 하고 싶다면 어떻게 해야할까?

 

클로저 안에 튜플(Tuple)의 값의 비교를 응용해서 정렬의 우선순위를 만들어보자.

튜플의 값 비교는 어떻게 작동하는 걸까?

예시를 통해 공부해보자.

 

// 튜플의 값 비교 예시

("가", 3) < ("나", 2) // true -> 앞에서 판단 끝
("나", 3) < ("가", 1) // false -> 앞에서 판단 끝
("가", 2) < ("가", 3) // true -> 앞이 동일하면 뒤에서 2순위 판단

("가", 2) < ("가", 3, 5) // 컴파일 에러 -> 튜플의 사이즈가 다름
("가", 2) < ("가", "3") // 컴파일 에러 -> 튜플의 타입이 다름

 

튜플끼리 값의 비교를 할 때는, 앞 요소끼리 각각 순서대로 크기 비교를 할 수 있다.

이 특징을 활용해보자.

 

// 튜플의 값 비교를 응용한 정렬의 우선순위 부여

print(myGrades.sorted { ($0[0], $0[1]) < ($1[0], $1[1]) })
// [["A", "라"], ["A", "마"], ["A", "바"], ["B", "가"], ["B", "다"], ["C", "나"]]
// ABC 순서가 1순위, 가나다 순서가 2순위로 적용됨

 

ABC 1순위, 가나다 2순위로 배열들이 정렬된 것을 확인할 수 있다.

그럼 만약, ABC 순서는 유지하고, 2순위 가나다 정렬을 거꾸로 하고 싶다면 어떻게 해야할까?

 

print(myGrades.sorted { ($0[0] < $1[0]) || ($0[1] > $1[1]) })
// [["A", "바"], ["A", "마"], ["A", "라"], ["B", "다"], ["B", "가"], ["C", "나"]]

 

클로저 안에 각각 값비교를 한 뒤에 OR(또는)를 의미하는 Logical operator|| 를 사용해서

ABC 순서를 유지하면서 가나다를 거꾸로 적용했다.

 

일단 결과는 얻어냈지만, 아직 나의 모자란 스위프트 문법 실력으로는 이 원리를 잘 모르겠다. 🥺

Comments