코틀린 언어 정리 4-9

Collection operations

Common operations( 일반적인 연산들 )

Collection Ordering( 컬렉션 정렬 )

컬렉션의 요소를 순서에 맞게 정렬합니다. 숫자의 크기, 문자열 코드 값에 의한 기본적인 정렬(Natural Order) 방식 또는 사용자가 임의로 구현한 정렬(Custom Order)을 적용할 수 있습니다.


코틀린의 정렬 함수들을 보면 원본 컬렉션에 정렬을 적용하는 경우와 원본 컬렉션을 수정하지 않고 새로운 컬렉션에 정렬 결과를 저장하는 함수가 이름 규칙으로 각각 구분되어 있습니다. 예를 들면 sort 는 정렬 결과를 원본 컬렉션에 적용 하고 sorted는 새로 생성한 컬렉션에 적용합니다. 이런 점 이외에 다른 기능은 대부분 동일하므로 여기서는 kotlinlang.org의 Collection Operations Overview처럼 "-ed"로 끝나는 함수에 대해서만 확인합니다.


Natural Order와 Custom Order의 구분

Natural Order

Comparable interface(compareTo)를 구현하는 객체(Comparable한 객체)에 적용되는 정렬 방법입니다. 다른 정렬 방법이 따로 지정되지 않습니다.면 기본적으로 Natural Order를 사용합니다. 대부분의 빌트인 타입들은 Comparable interface를 구현한 객체이므로 Natural Order로 동작 가능합니다.

Custom Order

Comparator를 만들어 객체를 비교하는 방법입니다. 비교 하고자 하는 객체가 Comparable 하지 않은 경우, 또는 Comparable하지만 Natural Order 이외에 다른 정렬 방법을 적용하려고 할 때 사용합니다.


Natural Order

클래스가 Natural Order를 지원하면, 이 클래스로 생성한 객체에 sorted 와 같은 정렬 함수 외에 비교 연산자( <, >, == 등 )도 사용할 수 있습니다. 참고로 Int, String등의 기본 타입(클래스)에는 이미 Comparable interface(Comparable<T>)가 구현되어 있어 정렬 함수와 연산자 모두 사용할 수 있습니다.

Comparable

컬렉션의 요소로 입력되는 객체의 타입(클래스)에서 Comparable interface를 구현하여( compareTo(other:T) ) 사용할 수 있습니다. 기존의 클래스에 Comparable 인터페이스를 구현하는 경우 이 클래스를 사용하는 모든 컬렉션의 Natural Order 기반 정렬에 영향을 줄 수 있으므로 주의해야 합니다. 일반적인 내용(공리, 상식 등)을 바탕으로 구현하는 경우 장점이 될 수 있지만, 원래의 데이터 타입(클래스)이 변형 된다는 점, 또 특수한 상황(일반적이지 않은 비교 방법 적용)에만 적용할 수 있는 내용을 바탕으로 구현할 때 의도치 않게 광범위하게 잘못 사용될 수 있다는 점 등에 주의 해야 합니다. 즉 어떤 클래스에 Comparable 인터페이스를 적용할 때에는 기존에 이 클래스를 사용한 컬렉션 및 정렬 등과 관련된 코드에 대해 모두 검토해봐야 하며, 앞으로도 이렇게 사용할 때 상식에 위배되는 문제가 없는지 등도 같이 검토해봐야 합니다.

구현 스타일

예제

class NormalComparable ( var value: String ): Comparable<NormalComparable> {

   override fun compareTo ( other: NormalComparable ): Int {

       return value.compareTo(other.value)

   }


   override fun toString (): String {

       return value

   }

}


fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.map { NormalComparable(it) }.sorted())

   println(list.sortedBy { NormalComparable(it) })

}


주요 함수들

sorted, sortedDescending


구현 방법 개요

정렬에 사용 되는 객체 타입(클래스)에 Comparable 인터페이스를 상속 받고 compareTo 함수를 구현하면 됩니다.


예제

//fun main ( args: Array<String> ) {

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sorted())

   println(list.sortedDescending())

}


Custom Orders

주요 함수들

sortedWith, compareBy


구현 방법 개요

Comparator 를 상속 받는 클래스를 정의하고 여기서 정렬에 사용되는 객체 타입(T)을 비교할 수 있는 추상 멤버 함수인 compare(p0: T, p1: T): Int 를 구현하면 됩니다.


Comparator

Comparator<T> 를 상속받는 T class(이름이 T인 Comparator)를 구현하여 만들 수 있습니다. T class에서는 추상함수인 compare(a, b)를 구현하여 사용할 수 있습니다. 기존 자료 구조 변경 없이 바로 사용할 수 있으며 사용하는 곳(인수로 입력하는 곳) 이외의 다른 부분에 대해 신경쓸 필요가 없는 장점이 있습니다.


Comparator 구현 스타일
Comparator 클래스를 구현하는 방법

예제

class NormalComparator: Comparator<String> {

   override fun compare ( p0: String, p1: String ): Int {

       return p0.compareTo(p1)

   }

}


fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sortedWith(NormalComparator()))

}


object키워드를 통한 익명 객체를 구현하는 방법

예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")


   var normalComparator = object: Comparator<String> {

       override fun compare ( p0: String, p1: String ): Int {

           return p0.compareTo(p1)

       }

   }


   println(list.sortedWith(normalComparator))

}


사용하는 곳에서 바로 구현 하는 방법( 멤버 함수가 1개인 인터페이스에만 적용되는 문법, 람다함수처럼 구현 )

예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")


   println(list.sortedWith(Comparator<String> { p0, p1 -> p0.compareTo(p1) }))

   println(list.sortedWith(Comparator { p0, p1 -> p0.compareTo(p1) }))

}


compareBy를 이용하여 비교 함수(compareTo)에 적용할 기준(selector)을 정하고 Comparable로 리턴하면 Comparator에서 Comparable을 이용하여 비교하는 구현을 제공하는 방법

우선 compareBy의 선언 중 하나를 확인해 보면 아래와 같습니다.


inline fun <T> compareBy(

    crossinline selector: (T) -> Comparable<*>?

): Comparator<T>


이 선언을 참고하여 전체 과정을 말로 풀어 보면, compareBy에 Comparable을 리턴하는 selector를 입력하면 이 Comparable 구현을 이용해 Comparator구현(compareTo에서 Comparable 인자 2개를 이용하여 비교하는 Comparator구현)을 만들어 리턴한 후 sortedWith에 입력하는 방법입니다.


보이지 않는 타입 변환이 많아 내부 동작을 이해하기는 조금 힘들지만 코드가 매우 짧은 형태입니다.


예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sortedWith(compareBy { it.first() })) // compareBy에 Comparable type을 리턴하는 selector를 입력하면 리턴되는 Comparable로 Comparator 타입이 생성되어 Comparator객체로 비교하게 됩니다.

}


compareBy 특징
여러 개의 비교 기준을 순차적으로 지정할 수 있고 이 순서에 따라 하위 정렬의 우선 순위가 결정됩니다.

예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sortedWith(compareBy(

       { it.length },

       { it }

   )))

}


사용자 정의 Comparator를 적용할 수 있습니다.

예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sortedWith(

       compareBy(

           Comparator { p0: Int, p1: Int -> p1 - p0 },

           { it.length }

       )

   ))

}


위의 두 가지를 동시에 적용하려면 thenBy를 사용할 수 있습니다.

예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")

   println(list.sortedWith(

       compareBy<String, Int>(

           Comparator { p0: Int, p1: Int -> p1 - p0 },

           { it.length }

       ).thenBy(

           Comparator { p0: String, p1: String -> p1.compareTo(p0) },

           { it }

       )

   )

   )

}


Comparator의 compare와 Comparable의 compareTo 리턴 값

함수 형식

Comparator<K>::compare(p0: T, p1: T): Int

Comparable<T>::compareTo(other: T): Int


compare 함수를 기준으로 T가 숫자형 데이터라고 가정해 보면, 단순하게 p0 - p1 연산을 수행한 결과로 볼 수 있습니다. 결과가 0보다 크면 p0가 p1보다 큰 것이고 0이면 p0와 p1이 같은 것으로 처리 됩니다. 0보다 작을 경우 p0가 p1보다 작은 것입니다. compareTo 함수도 마찬가지로 처리됩니다.(this - other)


Reverse Order

원본 컬렉션의 요소들을 역정렬하여 보여주거나, 새로운 컬렉션을 생성하여 제공합니다.

주요 함수들

reversed, asReversed

reversed

원본 컬렉션의 요소들을 역정렬한 새로운 컬렉션을 생성하여 리턴합니다.

reversed로 생성하여 리턴한 컬렉션은 원본과 별개 이므로 서로 영향을 미치지 않습니다..

asReversed

원본 컬렉션의 요소들을 역정렬한 view를 리턴합니다.

asReversed는 view를 리턴한 것으로 원본 컬렉션이 수정되면, update등의 특별한 기능 호출 없이 view에도 바로 반영이 됩니다. 마찬가지로 view를 수정하면 원본에 바로 반영이 됩니다.

원본과 view의 요소는 서로 동일한 인스턴스의 데이터를 사용하며 단지 접근하는 순서만 다릅니다.


예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")


   println("Reverse Order: reversed, asReversed 등의 역정렬 함수")

   var list_mutable = mutableListOf<String>().apply { addAll(list) }

   var reversedList = list_mutable.reversed()

   var asReversedListView = list_mutable.asReversed()

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView)

   println("-------------------------------------------------------------")


   println("Reverse Order: 원본 정렬 후 이전에 계산한 reversed, asReversed 결과값 확인")

   println("원본       :" + list_mutable.apply { sort() })

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view도 수정된 원본 list 기반으로 다시 계산되어 출력됩니다.

   println("-------------------------------------------------------------")



   println("Reverse Order: 원본에 요소 추가 후 이전에 계산한 reversed, asReversed 결과값 확인")

   println("원본       :" + list_mutable.apply { add("북극곰"); add("펭귄") })

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView)

   println("-------------------------------------------------------------")



   println("Reverse Order: view(asReversed 결과값)에 요소 추가 후 이전에 계산한 reversed, 원본 결과값 확인")

   asReversedListView.apply { add("늑대") }

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view에도 수정된 원본 list가 적용됩니다.

   println("-------------------------------------------------------------")


   println("Reverse Order: view(asReversed 결과값) 정렬 후 이전에 계산한 reversed, 원본 결과값 확인")

   asReversedListView.sort()

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view에도 수정된 원본 list가 적용됩니다.

   println("-------------------------------------------------------------")

}


Random Order

원본 컬렉션의 요소들을 무작위 순서로 정렬하여 새로운 컬렉션을 생성하여 리턴합니다.

주요 함수

shuffled

매개변수가 없는 함수, 난수 발생에 대한 세부적인 옵션 설정이 가능한 Random 객체를 입력받는 함수가 있습니다.


예제

fun test () {

   var list = listOf("개", "고양이", "돼지", "소", "호랑이", "개", "고양이", "개", "곰")


   println("Random Order: shuffled")

   println(list.shuffled())

   println(list.shuffled())

   println("-------------------------------------------------------------")

}



전체 예제

class NormalComparator: Comparator<String> {

   override fun compare ( p0: String, p1: String ): Int {

       return p0.compareTo(p1)

   }

}


class NormalComparable ( var value: String ): Comparable<NormalComparable> {

   override fun compareTo ( other: NormalComparable ): Int {

       return value.compareTo(other.value)

   }


   override fun toString (): String {

       return value

   }

}


class StringAA ( var value: String ) {}



fun test () {// 20190804

   var list = listOf("개", "돼지", "고양이", "소", "개", "말", "호랑이", "사자", "표범", "호랑이", "물범", "바다표범", "독수리")

   println("Natural Order: sorted, sortedDescending...Kotlin 언어에서 이미 제공되고 있는 기본적인 정렬 방법 사용")

   println(list.sorted())

   println(list.sortedDescending())

   println("-------------------------------------------------------------")


   println("Custom Order: sortedBy, sortedByDescending... 사용자가 정의한 정렬 기준을 적용하여 Comparable value를 구한 후 Natural Order 수행")

   println(list.sortedBy { it.last() })

   println(list.sortedByDescending { it.first() })

   println(list.sortedBy { it.length })

   println(list.sortedByDescending { it.length })

   println("-------------------------------------------------------------")


   println("Custom Order: sortedWith(Comparator<T>)... 사용자가 즉석에서 직접 구현한 정렬 방법 사용. Comparator는 부가적인 작업이 적고 구현하여 직접 적용한 곳 이외에는 영향을 주지 않으므로 기존 코드에 대한 고려 없이 바로 구현하여 사용하기 편합니다.")

   var normalComparator = object: Comparator<String> {

       override fun compare ( p0: String, p1: String ): Int {

           return p0.compareTo(p1)

       }

   }

   var normalComparator1 = Comparator<String> { p0, p1 -> p0.compareTo(p1) }


   println(list.sortedWith(Comparator<String> { p0, p1 -> p0.compareTo(p1) }))

   println(list.sortedWith(Comparator { p0, p1 -> p0.compareTo(p1) }))

   println(list.sortedWith(NormalComparator()))

   println(list.sortedWith(normalComparator))

   println(list.sortedWith(normalComparator1))

   println("-------------------------------------------------------------")


   println("Custom Order: compareBy를 통해 사용자 정의 selector를 입력 받아 Comparable<T>로 리턴한 후 Comparable<T>를 사용하여 compare를 구현하는 Comparator<T>를 생성하여 리턴하면 sortedWith에서 Comparator<T>를 이용하여 정렬을 수행하는 방법")

   println(list.sortedWith(compareBy { it.first() })) // compareBy를 통해 사용자 정의 비교를 Comparable<T> 구조로 변환 후 Comparable<T>를 사용하는 Comparator<T>를 생성하여 리턴하면 sortedWith에서 Comparator<T>를 이용하여 정렬을 수행하는 방법

   println("-------------------------------------------------------------")

   println("Custom Order: compareBy를 통해 여러 개의 selector를 순차적으로 적용할 수 있는 방법. 먼저 적용된 selector를 통해 실행된 비교 함수의 결과를 리턴함. 하지만 비교 함수 결과가 0(equal)이 나오면 다른 비교 함수를 실행하여 비교 결과를 다시 계산하여 리턴함.")

   println(list.sortedWith(compareBy(

       { it.length },

       { it }

   )))


   println(list.sortedWith(

       compareBy(

           Comparator { p0: Int, p1: Int -> p1 - p0 },

           { it.length }

       )

   ))


   println(list.sortedWith(

       compareBy<String, Int>(

           Comparator { p0: Int, p1: Int -> p1 - p0 },

           { it.length }

       ).thenBy(

           Comparator { p0: String, p1: String -> p1.compareTo(p0) },

           { it }

       )

   )

   )


   println("-------------------------------------------------------------")


   println("Natural Order: sorted, sortedDescending 등의 Natural Order 정렬 함수 호출 시 Comparable<T>를 통해 사용자 정의 정렬을 적용시킬 수 있는 방법")

   println(list.map { NormalComparable(it) }.sorted()) // list 전체를 Comparable<T> 로 변환한 후 정렬

   println(list.sortedBy { NormalComparable(it) }) // 원본 list의 각각의 요소에 접근하면서 즉시 Comparable<T>로 변환하여 비교 함수를 실행하여 정렬 수행



   // 익명 클래스를 이용한 Comparable<T> 구현. 조금 이상하긴 하나 동작함. String 타입을 다른 타입으로 바꾸면 컴파일 에러가 나는 것으로 보아 운좋게 돌아가는 코드임.

   // Comparable 인터페이스 구현 시 임시 객체는 사용하지 말아야 함.(상속 받은 객체의 정확한 타입을 전달하기 어려움)

   var normalComparableFactory = object {

       fun create ( inputValue: String ): Comparable<Comparable<String>> {

           return object: Comparable<Comparable<String>> {

               var value: String = inputValue

               override fun compareTo ( other: Comparable<String> ): Int {

                   return value.compareTo(other.toString())

               }


               override fun toString (): String {

                   return value

               }

           }

       }

   }

   var comparableCreated = list.map { normalComparableFactory.create(it) }

   println(list.map { normalComparableFactory.create(it) }.sorted())

   println("-------------------------------------------------------------")


   println("Reverse Order: reversed, asReversed 등의 역정렬 함수")

   var list_mutable = mutableListOf<String>().apply { addAll(list) }

   var reversedList = list_mutable.reversed()

   var asReversedListView = list_mutable.asReversed()

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView)

   println("-------------------------------------------------------------")


   println("Reverse Order: 원본 정렬 후 이전에 계산한 reversed, asReversed 결과값 확인")

   println("원본       :" + list_mutable.apply { sort() })

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view도 수정된 원본 list 기반으로 다시 계산되어 출력됩니다.

   println("-------------------------------------------------------------")



   println("Reverse Order: 원본에 요소 추가 후 이전에 계산한 reversed, asReversed 결과값 확인")

   println("원본       :" + list_mutable.apply { add("북극곰"); add("펭귄") })

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView)

   println("-------------------------------------------------------------")



   println("Reverse Order: view(asReversed 결과값)에 요소 추가 후 이전에 계산한 reversed, 원본 결과값 확인")

   asReversedListView.apply { add("늑대") }

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view에도 수정된 원본 list가 적용됩니다.

   println("-------------------------------------------------------------")


   println("Reverse Order: view(asReversed 결과값) 정렬 후 이전에 계산한 reversed, 원본 결과값 확인")

   asReversedListView.sort()

   println("원본       :" + list_mutable)

   println("reversed  :" + reversedList)

   println("asReversed:" + asReversedListView) // 원본 list가 수정되면 view에도 수정된 원본 list가 적용됩니다.

   println("-------------------------------------------------------------")


   println("Random Order: shuffled")

   println(list.shuffled())

   println(list.shuffled())

   println("-------------------------------------------------------------")

}


+ Recent posts