코틀린에서 CharSequenceString, StringBuilder 등 문자열과 관련된 클래스들이 구현하는 뼈대 인터페이스

val seq: CharSequence = "Hello"
println(seq.length) // 5
println(seq[2]) // l
println(seq.subSequence(1, 4)) // ell

// 접두사, 접미사 관련
val str1: CharSequence = "https://www.naver.com"
println(str1.startsWith("https://")) // true
println(str1.endsWith(".com")) // true
println(str1.removePrefix("https://")) // www.naver.com
println(str1.removeSuffix(".com")) // https://www.naver
println(str1.removeSurrounding("https://", ".com")) // www.naver

// 비어 있는지 검사하기
val emptyStr = ""
val whiteSpaces = " "
val nullStr: String? = null
println(emptyStr.isEmpty()) // true
println(whiteSpaces.isEmpty()) // false
println(emptyStr.isBlank()) // true
println(whiteSpaces.isBlank()) // true
println(nullStr.isNullOrEmpty()) // true
println(nullStr.isNullOrBlank()) // true

// 가공하기
// removeRange, padStart, padEnd, trimStart, trimEnd, trim, slice, subSequence, substring, reversed 등 제공

// 쪼개기
val hello: CharSequence = "안녕하세요.\n고맙습니다.\n반갑습니다."
val time: CharSequence = "2018-01-22"
println(hello.lines())
for (line in hello.lineSequence())
  println(line)
println(time.split('-'))
// [안녕하세요.,고맙습니다.,반갑습니다.]
// 안녕하세요.
// 고맙습니다.
// 반갑습니다.
// [2018, 01, 22]

// 문자열 찾아 바꾸기
val adage = "Love begets love."
println(adage.replace("love", "hate", ignoreCase = true))
println(adage.replaceFirst("love", "compliment", ignoreCase = true))
println(adage.replaceRange(5..10, "hello"))
// hate begets love.
// compliment begets love.
// Love hello love.

// 문자열 덧붙이기
val builder = StringBuilder().append("2018").append(" Pyeongchang")
var result = builder.toString()
println(result) // 2018 Pyeongchang

// 정규식 다루기
val regex = Regex("[0-9]+")
val str = "4324235"
val str2 = "324 6546 5432"
println(regex matches str) // true
println(regex matches str2) // false
println(regex.replace(str2, "숫자")) // 숫자 숫자 숫자

문자열 추가하고 병합하기 (substring)

s = "abcdef"
println(s.substring(0..2)) // abc
s = s.substring(0..1) + "x" + s.substring(2..s.length-1) // abxcdef

문자열 비교하기 (compare)

var s1 = "Hello Kotlin"
var s2 = "Hello KOTLIN"
// 같으면 0, s1<s2 이면 양수, 반대면 음수를 반환
println(s1.compareTo(s2))
println(s1.compareTo(s2, true)) // 대소문자 무시

StringBuilder 사용하기

: StringBuilder를 사용하면 문자열이 사용할 공간을 좀 더 크게 잡을 수 있기 때문에 문자열이 자주 변경되는 경우에 사용하면 좋습니다.

var s = StringBuilder("Hello")
s.append("World")
s.insert(10, "Added") // 인덱스 10번부터 추가
s.delete(5, 10) // 인덱스 5번부터 10번 전까지 삭제

기타 문자열 처리

: 소문자/대문자 변경(toLowerCase, toUpperCase), 특정 문자 단위로 잘라내기(split), 앞뒤 공백 제거(trim)

var deli = "Welcome to Kotlin"
val sp = deli.split(" ")
println(sp) // [Welcome, to, Kotlin]

// 하나 이상의 분리 문자를 지정
...
str.split("=", "-")

문자열을 정수로 반환하기 (toInt)

val number: Int = "123".toInt()

...
try {
  "12w".toInt()
} catch(e: NumberFormatException) {
  println(e.printStackTrace())
}

만일 숫자가 아닌 문자가 포함되었을 때 null을 반환받고자 한다면 toIntOrNull()을 사용할 수 있습니다.

형식 문자 사용하기 (format)

val pi = 3.1415926
val dec = 10
val s = "hello"
println("pi = %.2f, %3d, %s".format(pi, dec, s))

'Kotlin > String' 카테고리의 다른 글

문자열 클래스 String, StringBuffer, StringBuilder  (1) 2022.09.13
StringBuilder  (0) 2022.09.13
String  (0) 2022.09.12

String, StringBuffer, StringBuilder

자바(Java)에서 문자열을 다루는 대표적인 클래스(Class)로 String, StringBuffer, StringBuilder가 있습니다. 이 클래스들은 모두 CharSequence 인터페이스의 구현체 클래스입니다. CharSequence 인터페이스를 상속 받았다는 것은 이 클래스들 모두 내부적으로 문자열을 char[]로 다루고 있다고 보면 됩니다.

연산이 많지 않을때는 위에 나열된 어떤 클래스를 사용하더라도 문제가 발생할 가능성은 거의 없습니다. 그러나 연산횟수가 많아지거나 멀티쓰레드, Race Condition 등의 상황이 자주 발생 한다면, 각 클래스의 특징을 이해하고 상황에 맞는 적절한 클래스를 사용해야 합니다.

String

String은 불변(Immutable)의 속성을 지니고있습니다. 즉, String 타입으로 인스턴스가 한번 생성되면 변경이 안된다는 의미입니다.

String str = "피카츄"; str = str + " 라이츄"; System.out.println(str);

위 코드의 실행 결과는 아래와 같을 것입니다.

피카츄 라이츄

위의 예제를 분석할 때 "피카츄"라는 값을 가지고 있는 String 타입 참조변수 str이 가리키는 곳에 저장된 "피카츄"에 "라이츄" 문자열을 더해 "피카츄 라이츄"로 변경한 것으로 우리는 오해할 수 있습니다. 하지만, 기존에 "피카츄" 값이 들어가있던 참조변수 str이 "피카츄 라이츄"라는 값을 가지고 있는 새로운 메모리 영역을 가리키도록 변경되고 처음 선언했던 "피카츄"로 값이 할당되어 있던 메모리 영역은 가비지(Garbage)로 남았다가 GC(Garbage Collector)에 의해 소멸되는 것 입니다. String 클래스는 불변하기 때문에 문자열을 수정하는 시점에 새로운 String 인스턴스가 생성된 것입니다.

String 인스턴스

위와 같이 String은 불변성을 지니고있기 때문에 변하지 않는 문자열을 자주 읽어들이는 경우 String을 사용하면, 좋은 성능을 기대할 수 있습니다. 그러나 문자열 추가, 수정, 삭제 등의 연산이 자주 발생되는 로직에 String 클래스를 사용하면 힙 메모리(Heap Memory)에 많은 가비지(Garbage)가 생성되어 어플리케이션 성능에 악영향을 줍니다.

StringBuffer, StringBuilder

위의 String 클래스의 문제점을 해결하기 위해 Java는 가변적(Mutable)인 속성을 지닌 StringBuffer, StringBuilder 클래스를 제공하고 있습니다. String과 달리 StringBuffer와 StringBuilder는 가변적인 속성을 가지고 있기 때문에 .append() .delete() 등의 API를 이용하여 동일 객체내에서 문자열을 변경하는 것이 가능합니다. 따라서 문자열의 추가, 수정, 삭제가 자주 발생하는 경우라면 String 클래스가 아닌 StringBuffer와 StringBuilder 클래스를 사용하면 됩니다.

// StringBuffer 클래스
StringBuffer strBf = new StringBuffer("피카츄");
strBf.append(" 라이츄");
System.out.println(strBf);

// StringBuilder 클래스
StringBuilder strBd = new StringBuilder("피카츄");
strBd.append(" 라이츄");
System.out.println(strBd);
 

위의 코드를 실행하면 다음과 같은 결과가 출력됩니다.

피카츄 라이츄
피카츄 라이츄
 
 

StringBuffer, StringBuilder

StringBuffer와 StringBuilder의 차이점

동일한 API를 가지고 있는 StringBuffer와 StringBuilder의 차이점은 동기화 유무입니다. StringBuffer는 동기화 키워들를 지원하여 멀티쓰레드 환경에서 안전합니다(thread-safe). 참고로 String도 불변성을 가지기 때문에 마찬가지로 멀티쓰레드 환경에서 안전합니다.

반대로 StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일 쓰레드에서의 성능은 StringBuffer 보다 뛰어납니다.

 

정리

String
StringBuffer
StringBuilder
저장소
String pool
Heap
Heap
변경
NO
YES
YES
멀티쓰레드
안전
안전
불안전
동기화
YES
YES
NO
성능
빠름
느림
빠름

 

'Kotlin > String' 카테고리의 다른 글

CharSequence  (0) 2022.09.13
StringBuilder  (0) 2022.09.13
String  (0) 2022.09.12

Kotlin string은 참조만 되지 변경하기가 어렵다. Python도 그랬던것 같다. 아마 가볍게 만들려고 그런것 같다.

공부하던중  Kotlin에도 자료구조에 사용하기 좋은 stringBuilder라는 구조를 찾았다.

StringBuilder 클래스는 String처럼 문자열을 다루는 클래스로, String에 문자열을 넣고 인스턴스 생성 후에 값을 변경하지 못했던 점을 개선한 클래스라 할 수 있다. 추가적으로 내부적으로 문자열을 저장하기 위한 메모리 공간을 지닌다. 

이놈은 append도 되고 insert, delete도 된다.  처리후 toString으로 형변환을 시켜주면 된다.

 

StringBuilder stb = new StringBuilder(32);  
이때   예시처럼 작성을 하면 생성자의 인자로 전달되는 숫자인 32 크기만큼 문자를 저장할 수 있는 공간을 마련한다. length를 찍어보면 아직까지는 0이다 메모리공간만 마련하는듯

-append(값) : 문자열 추가한다. 재미있는건 어떤 타입도 다 문자열로 바꾼다.
-delete(시작, 끝) : 시작 인덱스부터 끝 인덱스까지의 내용을 삭제한다.
-insert(위치, 값) : 특정 원하는 위치에 문자열을 추가한다.
-replace(시작, 끝, 값) : 시작 인덱스부터 끝 인덱스 이전까지의 내용을 추가하고자 하는 '값'으로 문자열을 대체한다.
-reverse() : 저장해놨던 문자열의 내용을 뒤집는다.
-substring(시작, 끝) : 저장된 문자열 중에서 시작부터 끝 이전까지의 내용만 담은 String 인스턴스의 값을 반환한다.
-deleteCharAt(위치) :  특정 위치의 값만 삭제한다.

fun main(args : Array<String>) {
    /*
    기존 참조하던 "abc" 뒤에 "def"를 추가하는 것이 아니라
    추가된 문자열 "abcdef"를 새로 생성하고 참조하는 것이다.
    그러면 쓰레기 "abc"가 남게되며 이러한 과정이 많아지면 비효율적
    var s1 = "ab"
    s1 += "cd"
    */

    val str = StringBuilder()   // 빈 생성자
    println("1. 문자열 : $str")
    /* 추가 */
    str.append(5)               // Int 타입
    str.append("man")          // String 타입
    str.append(' ')             // Char 타입
    str.append(true)            // Boolean 타입
    str.append(5431)
    str.append(" is ")
    str.append("women")

    println("2. 문자열 : $str")
    println("index=0 : ${str[0]}, index=5 : ${str[5]}")

    /* 삭제 */
    str.deleteCharAt(0) // index
    str.delete(8, 12)    // [start, end)
    println("after delete 0, 8..12 : $str")

    println("=========================================")
    val temp: String = "ABCD"
    val str2 = StringBuilder(temp)  // String을 받는 생성자
    println("1. 문자열 : $str2")
    println("길이 = ${str2.length}")
    println("뒤집기 = ${str2.reverse()}")

    str2.insert(0, "front+")   // index=0 위치에 추가
    println("2. insert front : $str2")

    val li = listOf("abc", "def", "ghi")
    str2.append(li.joinToString(""))
    println("3. append abcdefghi : $str2")

    println("4-1. 부분 문자열 : ${str2.substring(6)}")       // [6, length)
    println("4-2. 부분 문자열 : ${str2.substring(6, 9)}")        // [6, 8)
    println("4-3. 부분 문자열 : ${str2.substring(6..9)}")    // [6, 9]

    println("=========================================")
    val str3 = str.toString()   // String Type으로 저장
    if(str3 is String) println("String Type")
}

 

'Kotlin > String' 카테고리의 다른 글

CharSequence  (0) 2022.09.13
문자열 클래스 String, StringBuffer, StringBuilder  (1) 2022.09.13
String  (0) 2022.09.12

https://hyperskill.org/tracks

 

JetBrains Academy — Learn programming by building your own apps

Get theory, practice coding and move beyond programming challenges to building your own working projects.

hyperskill.org

 

알고리즘 풀때 스트링을 자주쓰는 편인데 코틀린 String에 대해 잘 안나와 있어 코틀린 사이트에서 추려서 정리해봤다

앞 뒤문자를 짜르는 명령어가 필요했는데 결국 substring을 이용해서 만들었다고 한다.

drop 은 문자열의 앞이나 뒷부분을 자를 때 사용된다.

내부적으로는 substring 으로 구현되어 있다.

  • drop : 앞에서부터 n 개의 문자를 제거한 String 을 반환
  • dropLast: 뒤에서부터 n 개의 문자를 제거
  • dropWhile, dropLastWhile: 조건을 만족하지 않는 문자가 나올때까지 제거
  • 파이썬같이 insert나 append를 찾고 싶었어나 배열은 자료구조상 안된 arrayList에서는 가능한것 같음.
val length: Int, length : Returns the length of this character sequence.
fun compareTo(other: String): Int compare() : 스트링을 비교해서 같으면 0 다르면 차이를 int로 리턴
fun equals(other: Any?): Boolean equals(): 스트링을 비교해서 같으면 true 리턴
fun get(index: Int): Char get(idx): 지정된 index의 문자를 리턴
fun hashCode(): Int hashCode() 문자열의 해시코드를 생성
operator fun plus(other: Any?): String 2개의 스트링을 합쳐주는 연산자
fun subSequence(start: Int, end: Int): CharSequence 지정된 범위의 Char Sequence를 리턴
fun toString(): String 스트링을 리턴 ?
val String.cstr: CValues<ByteVar>  
val CharSequence.indices: IntRange Returns the range of valid character indices for this char sequence.
val CharSequence.lastIndex: Int 스트링의 마지막 문자의 인덱스를 리턴 비었으면 -1 리턴
val String.utf16: CValues<UShortVar> utf8 utf16 utf32등도 있다.
val String.wcstr: CValues<UShortVar>  
fun CharSequence.all(predicate: (Char) -> Boolean): Boolean Returns true if all characters match the given predicate.
fun CharSequence.any(): Boolean Returns true if char sequence has at least one character.
fun CharSequence.asIterable(): Iterable<Char> Creates an Iterable instance that wraps the original char sequence returning its characters when being iterated.
fun CharSequence.asSequence(): Sequence<Char> Creates a Sequence instance that wraps the original char sequence returning its characters when being iterated.
fun <K, V> CharSequence.associate(
    transform: (Char) -> Pair<K, V>
): Map<K, V>
associate
Returns a Map containing key-value pairs provided by transform function applied to characters of the given char sequence.
fun String.capitalize(locale: Locale): String  
fun String.decapitalize(locale: Locale): String  
fun CharSequence.chunked(size: Int): List<String> 지정된 사이즈로 문자시퀀스를 짤라 리스트를 만든다.
operator fun CharSequence.contains(
    other: CharSequence,
    ignoreCase: Boolean = false
): Boolean
문자열이 자신에게 포함되어 있는지
fun CharSequence.count(): Int 스트링길이
fun String.drop(n: Int): String 앞 문자를 n개 지운 스트링을 리턴한다
fun String.dropLast(n: Int): String 마지막 문자부터 n개 지운 스트링을 리턴한다.
fun String.dropWhile(predicate: (Char) -> Boolean): String 앞에서 부터 조건대로 문자를 제거
fun String.dropLastWhile(
    predicate: (Char) -> Boolean
): String
뒤에서 부터 조건대로 문자를 제거
val string = "<<<First Grade>>>"
println(string.drop(6)) // st Grade>>>
println(string.dropLast(6)) // <<<First Gr
println(string.dropWhile { !it.isLetter() }) // First Grade>>>, 앞쪽 문자가 아닌걸 제거
println(string.dropLastWhile { !it.isLetter() }) // <<<First Grade, 뒤쪽 문자가 아닌걸 제거
fun String.filter(predicate: (Char) -> Boolean): String Returns a string containing only those characters from the original string that match the given predicate.
fun String.filterIndexed(
    predicate: (index: Int, Char) -> Boolean
): String
Returns a string containing only those characters from the original string that match the given predicate.
fun String.filterNot(predicate: (Char) -> Boolean): String Returns a string containing only those characters from the original string that do not match the given predicate.
fun CharSequence.find(predicate: (Char) -> Boolean): Char? find: Returns the first character matching the given predicate, or null if no such character was found.
fun CharSequence.first(): Char Returns the first character.
fun CharSequence.firstOrNull(): Char? Returns the first character, or null if the char sequence is empty.
fun CharSequence.isEmpty(): Boolean Returns true if this char sequence is empty (contains no characters).
operator fun CharSequence.iterator(): CharIterator Iterator for characters of the given char sequence.
fun CharSequence.lines(): List<String> Splits this char sequence to a list of lines delimited by any of the following character sequences: CRLF, LF or CR.
fun <R> CharSequence.map(transform: (Char) -> R): List<R> map: Returns a list containing the results of applying the given transform function to each character in the original char sequence.
fun <R : Comparable<R>> any_iterable<R>.maxOf(selector: (Char) -> R maxOf: Returns the largest value among all values produced by selector function applied to each character in the char sequence.
fun <R : Comparable<R>> any_iterable<R>.minOf(selector: (Char) -> R minOf: Returns the smallest value among all values produced by selector function applied to each character in the char sequence.

끝이 없다. 더 보고 싶은 분은 아래 코틀린 사이트에서 공부하시길

 

 

 

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/

 

String - Kotlin Programming Language

 

kotlinlang.org

 

'Kotlin > String' 카테고리의 다른 글

CharSequence  (0) 2022.09.13
문자열 클래스 String, StringBuffer, StringBuilder  (1) 2022.09.13
StringBuilder  (0) 2022.09.13

1. Collection Sort

Kotlin 에서는 Collection 을 정렬하기 위한 여러가지 유틸리티들을 제공합니다.

 

1.1. Sort, Sorted

가장 쉬운 방법은 sort 메소드를 호출하는 겁니다.

기본적으로 오름차순으로 정렬합니다.

val list = mutableListOf(1, 2, 7, 6, 5, 6)
list.sort()
println(list)  // [1, 2, 5, 6, 6, 7]

 

sort 메소드는 해당 Collection 의 원소 위치가 변경됩니다.

기존 Collection 은 그대로 둔 채 새로운 Collection 으로 받길 원한다면 sorted 메소드를 사용해야 합니다.

sorted 메소드를 사용하면 기존 Collection 은 변하지 않습니다.

val list = mutableListOf(1, 2, 7, 6, 5, 6)  //뮤터블 리스트 초기화 생성
val sorted = list.sorted()  //소팅해서 새로운 리슽 생성
println(sorted)  // [1, 2, 5, 6, 6, 7]  /소팅되었음
println(list)    // [1, 2, 7, 6, 5, 6] (sorted 를 사용했기 때문에 변하지 않음)

 

내림차순으로 정렬하고 싶다면 sortByDescending 를 사용하거나 reverse 메소드를 사용하면 됩니다.

마찬가지로 sortedByDescending 를 사용하면 원래 Collection 의 변경 없이 내림차순으로 정렬된 값을 구할 수 있습니다.

// 1. sortByDescending 로 내림차순 정렬
list.sortByDescending { it }

val sorted = list.sortedByDescending { it }  //내림차순 정열

// 2. reverse 사용해서 정렬 후 뒤집기
list.sort()  //자신을 소팅한후
list.reverse()  //순서를 바꾼다.

val sorted = list.sorted().reversed()   //.연산자를 이용 한번에 붙여써도

 

1.2. SortBy

만약 Object 의 특정 Property 들을 기준으로 정렬하고 싶다면 sortBy 메소드를 사용하면 됩니다.

sortBy 메소드는 Object 를 받아서 Property 를 반환하는 Lamdba 식을 파라미터로 받습니다.

val list = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
list.sortBy { it.second }
println(list)  // [(1, a), (2, b), (7, c), (5, c), (6, d), (6, e)]

sort 와 마찬가지로 기존 Collection 의 변경 없이 정렬된 값을 받고 싶다면 sortedBy 를 사용하면 됩니다.

그리고 내림차순을 지원하는 sortByDescending 도 있습니다.

 

1.3. SortWith

sortWith 메소드를 사용하면 여러 가지 조건을 섞어서 정렬할 수 있습니다.

sortWith 메소드는 Comparator 를 파라미터로 받습니다.

(Kotlin 에서 Comparator 를 생성하는 여러가지 방법은 다음 챕터에서 다룹니다)

val list = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
list.sortWith(compareBy({it.second}, {it.first}))
println(list)  // [(1, a), (2, b), (5, c), (7, c), (6, d), (6, e)]

 

위 Collection 은 it.second(문자) 로 먼저 정렬된 후에 it.first(숫자) 로 정렬됩니다.

그리고 역시 sortedWith 메소드가 존재하며, 역순으로 정렬할때는 reverse 를 사용하거나 Comparator 를 반대로 수정하면 됩니다.

 

2. Comparison

Kotlin 은 Comparator 를 만들기 위해 kotlin.comparisons 라는 유용한 패키지를 제공합니다.

이 챕터에서는 아래 컨텐츠를 다룹니다.

  • Comparator creation
  • Handling of null values
  • Comparator rules extension

 

2.1. Comparator Creation

Kotlin 은 Comparator 를 생성하는 여러 팩토리 메서드를 제공합니다.

 

2.1.1. naturalOrder

가장 간단한 생성 메서드는 naturalOrder() 입니다.

아무런 파라미터를 필요로 하지 않으며 오름차순을 기본으로 합니다.

val ascComparator = naturalOrder<Long>()

 

2.1.2. compareBy

여러 개의 속성을 사용하고 싶다면 compareBy 메소드를 사용하면 됩니다.

파라미터로는 Comparable 를 리턴하는 정렬 규칙을 여러 개 사용할 수 있습니다.

그럼 넘겨진 규칙들은 순차적으로 호출 되며 원소들을 정렬합니다.

만약 먼저 나온 규칙에서 원소의 우열이 가려져 정렬 처리가 되었다면 뒤의 규칙들은 확인하지 않습니다.

val complexComparator = compareBy<Pair<Int, String?>>({it.first}, {it.second})

위 코드에서 it.first 값을 사용해 먼저 비교를 하고 값이 같은 경우에만 it.second 비교까지 이루어집니다.

 

2.1.3. Comparator

간단하게 new Comparator 를 선언해서 만들 수도 있습니다.

자바와 마찬가지로 두 원소에 대한 비교 조건을 넣어줘야 합니다.

val caomparator = Comparator<Int> { a, b -> a.compareTo(b) }

 

2.2. Handling of null Values

정렬하려는 Collection 이 null 값을 갖고 있을 수도 있습니다.

nullsFirst 또는 nullsLast 와 함께 Comparator 를 사용하면 null 값을 가장 처음 또는 가장 마지막에 위치하도록 설정할 수 있습니다.

val list = mutableListOf(4, null, 1, -2, 3)

list.sortWith(nullsFirst())  // [null, -2, 1, 3, 4]

list.sortWith(nullsLast())  // [-2, 1, 3, 4, null]

list.sortWith(nullsFirst(reverseOrder()))  // [null, 4, 3, 1, -2]

list.sortWith(nullsLast(compareBy { it }))  // [-2, 1, 3, 4, null]

 

2.3. Comparator Rules Extension

Comparator 오브젝트는 추가적인 정렬 규칙과 혼합되거나 확장할 수 있습니다.

kotlin.comparable 패키지에 있는 then 키워드를 활용하면 됩니다.

첫 번째 비교의 결과가 동일할 때만 두번째 비교가 이루어집니다.

val students = mutableListOf(21 to "Helen", 21 to "Tom", 20 to "Jim")

val ageComparator = compareBy<Pair<Int, String?>> {it.first}
val ageAndNameComparator = ageComparator.thenByDescending {it.second}

// [(20, Jim), (21, Tom), (21, Helen)]
println(students.sortedWith(ageAndNameComparator))

위 코드는 나이가 어린 순으로 먼저 정렬하고 나이가 같으면 이름을 알파벳 역순으로 정렬합니다.

 

Reference

'Kotlin > Collection' 카테고리의 다른 글

Collection List, Set, Map  (0) 2022.09.12

Collection

Collection(콜렉션)은 대부분의 프로그래밍 언어에 있는 자료구조입니다. 자바의 List, Map, Set 등을 Collection이라고 합니다. Collection은 Generic으로 구현이 되어 다양한 타입과 함께 사용될 수 있습니다.

코틀린의 Collection은 기본적으로 코트린의 다른 자료구조와 마찬가지로 Mutable(변할 수 없는)과 Immutable(불변의)을 별개로 지원합니다. Mutable로 생성하면 추가, 삭제가 가능하지만, Immutable로 생성하면 수정이 안됩니다.

코틀린의 콜렉션들은 아래 그림과 같은 상속 구조 갖고 있습니다.

(출처: kotlinlang.com)

// Collection 인터페이스
val list: Collection<Int> = listOf(18, 1, 1, 4)  //초기화와 함께 선언
println(list.size) // 4
println(list.isEmpty()) // false (isNotEmpty, none도 있음)
println(18 in list) // true

// MutableCollection 인터페이스
val mutableList: MutableCollection<Int> = mutableListOf(1, 2, 4, 2, 3, 2, 5) // arrayListOf와 동일
mutableList.add(1) 
println(mutableList) // [1, 2, 4, 2, 3, 2, 5, 1]
mutableList.addAll(listOf(3, 2, 4))
println(mutableList) // [1, 2, 4, 2, 3, 2, 5, 1, 3, 2, 4]
mutableList.remove(1)
println(mutableList) // [2, 4, 2, 3, 2, 5, 1, 3, 2, 4]
mutableList.removeAll(listOf(1, 2))
println(mutableList) // [4, 3, 5, 3, 4]
mutableList.retainAll(listOf(3, 5))
println(mutableList) // [3, 5, 3]
mutableList.clear() 
println(mutableList) // []

Collection 반복

val fruits = listOf("apple", "banana", "kiwi")  //리스트 생성
for (item in fruits) {   //리스트 반복하기
  println(item)
}
// 요소의 인덱스를 통해 List에 접근하기
for (index in fruits.indices) {   // 인덱스 지정
  println("fruits[$index] = ${fruits[index]}")   //[]연산자 이용 인덱스 접근
}
// while 루프 이용하기
var index = 0
while (index < fruits.size) {
  println("fruits[$index] = ${fruits[index]}")
  index++
}

 

emptyList() / listOfNotNull() 함수

비어 있는 리스트를 생성하려면 emptyList<>()를 사용할 수 있습니다. 이때는 반드시 형식 매개변수를 지정합니다. listOfNotNull()로 초기화하면 null을 제외한 요소만 반환해 List를 구성할 수 있습니다.

val emptyList: List<String> = emptyList<String>()  // 빈리스트 생성
val nonNullsList: List<Int> = listOfNotNull(2, 45, 2, null, 5, null)  //Null을 제외한 리스트
println(nonNullsList) // [2, 45, 2, 5]

 

List

List는 데이터가 저장하거나 삭제될 때 순서를 지키는 Collection입니다. List는 Mutable(변할 수 없는)과 Immutable(불변의)을 모두 지원합니다.
이 타입의 구현체는 ArrayList, LinkedList 두 가지가 있다 

// List 인터페이스 
val list: List<Double> = listOf(20.18, 1.14, 9.15, 1.14) // 불변형
val list2: List<Double> = mutableListOf(20.18, 1.14, 9.15, 1.14) // 가변형 

val list3: List<Int> = LinkedList<Int>()   //LinkedList와
val list4: List<Int> = ArrayList<Int>()     // ArrayList 2가지가 있다.

println(list[0]) // 20.18
println(list.indexOf(1.14)) // 1
println(list.indexOf(9.31)) // -1
println(list.lastIndexOf(1.14)) // 3
println(list.subList(0, 3)) // [20.18, 1.14, 9.15]

println(list.size) // List 크기
println(list.get(0)) // 해당 인덱스의 요소 가져오기
println(list.contains(2.0)) // 포함 여부 확인하기


List 인터페이스에는 indices()(인덱스의 범위), contains(), get()/elementAt()(해당 인덱스의 요소를 읽기), last(), first(), firstOrNull, lastOrNull, count(), distinct(), joinToString()(요소들의 값을 문자열로 변환하여 결합), single(요소 하나 반환), singleOrNull, binarySearch, find 등이 있다

이외에도 MutableList 인터페이스에는 slice(), containsAll(), asReversed()(모든 요소를 역순으로 저장한 새로운 List를 생성), toList(), toMutableList(), toSet(), toMutableSet(), toHashSet() 등이 있다
LinkedList에서만 사용할 수 있는 함수들은 addFirst(), addLast(), removeFirst(), removeLast() 등이 있다
그 외에도 max(), min() 등의 함수가 존재한다

List : Immutable

listOf<타입>(아이템, )로 Immutable List를 생성 및 초기화를 할 수 있습니다. 코틀린은 아이템의 타입을 추론하기 때문에 타입을 생략해도 됩니다. Immutable이기 때문에 get만 가능합니다. List의 getter는 자바처럼 get(index)도 지원하고 배열처럼 [index]도 지원합니다. 코틀린은 간결하고 직관적인 배열같은 표현 방식을 선호합니다.

val fruits= listOf<String>("apple", "banana", "kiwi", "peach")
// val fruits= listOf("apple", "banana", "kiwi", "peach") -> 타입 생략 가능
println("fruits.size: ${fruits.size}")
println("fruits.get(2): ${fruits.get(2)}")
println("fruits[3]: ${fruits[3]}")
println("fruits.indexOf(\"peach\"): ${fruits.indexOf("peach")}")

위의 코드를 실행한 결과입니다.

fruits.size: 4
fruits.get(2): kiwi
fruits[3]: peach
fruits.indexOf("peach"): 3

 

List : Mutable

수정가능한 List는 mutableListOf로 선언합니다. listOf와 대부분 비슷하지만, 추가 및 삭제가 가능합니다. 자바의 Collection에 익숙하시다면 remove, add, addAll, removeAt 등은 이미 알고 계실 것입니다. 또 slice(), containsAll(), asReversed()(모든 요소를 역순으로 저장한 새로운 List를 생성), toList(), toMutableList(), toSet(), toMutableSet(), toHashSet() 등이 있다
LinkedList에서만 사용할 수 있는 함수들은 addFirst(), addLast(), removeFirst(), removeLast() 등이 있다
그 외에도 max(), min() 등의 함수가 존재한다

val fruits= mutableListOf<String>("apple", "banana", "kiwi", "peach")
fruits.remove("apple")
fruits.add("grape")
println("fruits: $fruits")

fruits.addAll(listOf("melon", "cherry"))
println("fruits: $fruits")
fruits.removeAt(3)
println("fruits: $fruits")

위의 코드를 실행한 결과입니다.

fruits: [banana, kiwi, peach, grape]
fruits: [banana, kiwi, peach, grape, melon, cherry]
fruits: [banana, kiwi, peach, melon, cherry]

그 외에도 replace, replaceAll, contains, forEach 등의 메소드도 지원합니다.

불변형 List를 가변형으로 변환하기

// 불변형 List를 가변형으로 변환하기
val names: List<String> = listOf("one", "two", "three") // 불변형 List

val mutableNames = names.toMutableList()  // 새로운 가변형 List 가 만들어짐
mutableNames.add("four")
println(mutableNames)
// [one, two, three, four]

Set

Set은 동일한 아이템이 없는 Collection입니다. Set의 아이템들의 순서는 특별히 정해져 있지 않습니다. Set은 null 객체를 갖고 있을 수 있습니다. 동일한 객체는 추가될 수 없기 때문에 null도 1개만 갖고 있을 수 있습니다. List와 같이 Set도 Immutable과 Mutable을 별개로 지원합니다.  이 타입의 구현체는 HashSet, LinkedHashSet, TreeSet 세 가지가 있다 

Set : Immutable

setOf<타입>(아이템들)로 객체를 생성할 수 있습니다. 다음과 같이 객체 아이템들을 확인할 수 있습니다.

val numbers = setOf<Int>(33, 22, 11, 1, 22, 3)
println(numbers)
println("numbers.size: ${numbers.size}")
println("numbers.contains(1): ${numbers.contains(1)}")
println("numbers.isEmpty(): ${numbers.isEmpty()}")

실행 결과입니다.

[33, 22, 11, 1, 3]
numbers.size: 5
numbers.contains(1): true
numbers.isEmpty(): false

forEach 또는 Iterator 등으로 모든 객체를 탐색할 수도 있습니다.

Set : Mutable

Mutable은 mutableSetOf<타입>(아이템들) 로 생성할 수 있습니다. Mutable이기 때문에 추가, 삭제가 가능합니다. List와 비슷한 메소드들을 지원합니다.

val numbers = mutableSetOf<Int>(33, 22, 11, 1, 22, 3)
println(numbers)
numbers.add(100)
numbers.remove(33)
println(numbers)
numbers.removeIf({ it < 10 }) // 10 이하의 숫자를 삭제
println(numbers)

실행 결과 입니다.

[33, 22, 11, 1, 3]
[22, 11, 1, 3, 100]
[22, 11, 100]

sortedSetOf() 함수 / linkedSetOf() 함수

sortedSetOf() 함수는 자바의 TreeSet 컬렉션을 정렬된 상태로 반환합니다. 이 함수를 사용하려면 java.util.* 패키지를 임포트해야 합니다. TreeSet는 저장된 데이터의 값에 따라 정렬되는데, 일종의 개선된 이진 탐색 트리(Binary-search Tree)인 레드 블랙 트리(RB tree: Red-Black tree) 알고리즘을 사용해 자료구조를 구성합니다. HashSet보다 성능이 좀 떨어지고 데이터를 추가하거나 삭제하는 데 시간이 걸리지만 검색과 정렬이 뛰어나다는 장점이 있습니다.

linkedSetOf()는 자바의 LinkedHashSet 자료형을 반환하는 헬퍼 함수입니다. 링크드 리스트를 사용해 구현된 해시 테이블에 요소를 저장합니다. HashSet, TreeSet보다 느리지만 데이터를 가리키는 포인터 연결을 통해 메모리 저장 공간을 좀 더 효율적으로 사용할 수 있습니다.

import java.util.*

// 자바의 java.util.TreeSet 선언
val intsSortedSet: TreeSet<Int> = sortedSetOf(4, 1, 7, 2)
intsSortedSet.add(6)
intsSortedSet.remove(1)
println("intsSortedSet = ${intsSortedSet}")
intsSortedSet.clear()  // 모든 요소 삭제
println("intsSortedSet = ${intsSortedSet}")

// Linked List를 이용한 HashSet
val intsLinkedHashSet: java.util.LinkedHashSet<Int> = linkedSetOf(35, 21, 76, 26, 75)
intsLinkedHashSet.add(4)
intsLinkedHashSet.remove(21)

println("intsLinkedHashSet $intsLinkedHashSet")
intsLinkedHashSet.clear()
println(intsLinkedHashSet)

Map

Map은 key와 value를 짝지어 저장하는 Collection입니다. Map의 key는 유일하기 때문에 동일한 이름의 key는 허용되지 않습니다.에 Map 또한 Immutable과 Mutable을 별개로 지원합니다. 이 타입의 구현체는  HashMap,  LinkedHashMap, TreeMap 세 가지가 있다

// Map 인터페이스
val map: Map<String, String> = mapOf("Apple" to "사과", "Banana" to "바나나") // 불변형
println(map.size) // 2
println(map.keys) // [Apple, Banana]
println(map.values) // [사과, 바나나]
println(map.entries) // [Apple=사과, Banana=바나나]
println(map.containsKey("Cocoa")) // false
println(map.containsValue("바나나")) // true
println(map["Apple"]) // 사과
println(map.getOrDefault("Cocoa", "코코아")) // 코코아

// 맵 순회하기
for ((key, value) in map) {
  println("key=$key, value=$value")
}

// MutableMap 인터페이스
val map: MutableMap<String, String> = mutableMapOf() // 가변형 
println(map) // {}
println(map.put("Hi", "안녕1")) // null
println(map) // {Hi=안녕1}
map["Hi"] = "안녕2"
println(map) // {Hi=안녕2}
map.putAll(mapOf("How is it going?" to "잘 지내?", "Bye!" to "잘 가!"))
println(map) // {Hi=안녕2, How is it going?=잘 지내?, Bye!=잘 가!}
println(map.remove("Hi")) // 안녕2
println(map) // {How is it going?=잘 지내?, Bye!=잘 가!}
println(map.remove("Bye!", "잘 가!")) // true
println(map) // {How is it going?=잘 지내?}
map.clear()
println(map) {}

이외에도 getOrPut(k, v), toList(), toMutableMap(), toMap(), toSortedMap(), putAll() 등이 있다

Map의 기타 자료구조

앞에서 Set의 선언된 형태와 비슷하게 Map에서도 자바의 HashMap, SortedMap, LinkedHashMap을 사용할 수 있습니다

import java.util.*

// java.util.HashMap의 사용
val hashMap: HashMap<Int, String> = hashMapOf(1 to "Hello", 2 to "World")
println("hashMap = $hashMap")

// java.util.SortedMap 사용
val sortedMap: SortedMap<Int, String> = sortedMapOf(1 to "Apple", 2 to "Banana")
println("sortedMap = $sortedMap")

// java.util.LinkedHashMap의 사용
val linkedHash: LinkedHashMap<Int, String> = linkedMapOf(1 to "Computer", 2 to "Mouse")
println("linkedHash = $linkedHash")

Map : Immutable

Map은 mapOf<key type, value type>(아이템)로 생성할 수 있습니다. 아이템은 Pair객체로 표현하며, Pair에 key와 value를 넣을 수 있습니다. Pair(A, B)는 A to B로 간단히 표현이 가능합니다. 이런 문법이 가능한 것은 to가 Infix이기 때문입니다.

val numbersMap = mapOf<String, String>(
    "1" to "one", "2" to "two", "3" to "three")
println("numbersMap: $numbersMap")
val numbersMap2 = mapOf(Pair("1", "one"), Pair("2", "two"), Pair("3", "three"))
println("numbersMap2: $numbersMap2")

// 실행해보면 모두 동일한 값을 갖고 있습니다.
// numbersMap: {1=one, 2=two, 3=three}
// numbersMap2: {1=one, 2=two, 3=three}

Map의 데이터를 읽는 것도 다른 Collection과 유사합니다. getter는 get(index)와 [index]를 모두 지원합니다. 코틀린은 배열 방식을 선호합니다. keys와 values는 key와 value만으로 구성된 Set을 리턴해줍니다.

val numbersMap = mapOf<String, String>(
    "1" to "one", "2" to "two", "3" to "three")
println("numbersMap.get(\"1\"): ${numbersMap.get("1")}")
println("numbersMap[\"1\"]: ${numbersMap["1"]}")
println("numbersMap[\"1\"]: ${numbersMap.values}")
println("numbersMap keys:${numbersMap.keys}")
println("numbersMap values:${numbersMap.values}")

for (value in numbersMap.values) {
    println(value)
}

실행 결과입니다.

numbersMap.get("1"): one
numbersMap["1"]: one
numbersMap["1"]: [one, two, three]
numbersMap keys:[1, 2, 3]
numbersMap values:[one, two, three]
one
two
three

Map : Mutable

Mutable은 mutableMapOf<key type, value type>(아이템)로 생성합니다. 객체 추가는 put 메소드이며, Pair를 사용하지 말고 인자로 key와 value를 넣어주면 됩니다. put도 배열 방식을 지원합니다. 그 외에 자바의 Map과 유사합니다.

val numbersMap = mutableMapOf<String, String>(
        "1" to "one", "2" to "two", "3" to "three")
println("numbersMap: $numbersMap")

numbersMap.put("4", "four")
numbersMap["5"] = "five"
println("numbersMap: $numbersMap")

numbersMap.remove("1")
println("numbersMap: $numbersMap")

numbersMap.clear()
println("numbersMap: $numbersMap")

실행 결과 입니다.

numbersMap: {1=one, 2=two, 3=three}
numbersMap: {1=one, 2=two, 3=three, 4=four, 5=five}
numbersMap: {2=two, 3=three, 4=four, 5=five}
numbersMap: {}

Collection 상속

List와 Set은 Collection을 상속합니다. 이 클래스들은 Collection에 대입할 수 있기 때문에 아래와 같이 사용할 수 있습니다.

fun printAll(strings: Collection<String>) {
    for(s in strings) print("$s ")
    println()
}

val stringList = listOf("one", "two", "one")
printAll(stringList)

val stringSet = setOf("one", "two", "three")
printAll(stringSet)

실행 결과 입니다.

one two one
one two three

컬렉션 선택하기

List 인터페이스의 ArrayList와 LinkedList는 기본적으로는 무작위나 순차적인 처리 속도가 더 빠른 ArrayList를 선택하는 것이 좋다. 그러나 List의 중간에서 추가나 삭제를 자주 한다면 LinkedList를 사용하는 것이 좋다. 물론, 정해진 수의 요소들을 처리할 때는 배열을 사용하는 게 좋다
Set 인터페이스에는 LinkedHashSet, HashSet, TreeSet이 있다. 기본적으로는 코클린에서 생성 시 제공되는 LinkedHashSet을 사용하는 것이 좋으며, 무작위 검색의 성능이 매우 중요하다면 HashSet을, 그리고 요소의 일정한 순서 유지와 처리가 중요하다면 TreeSet을 사용하는 것이 좋다
Map 인터페이스는 LinkedHashMap, HashMap, TreeMap이 있다. 코틀린은 기본적으로 LinkedHashMap을 사용한다. HashMap은 무작위 검색 시에만 조금 더 유리하며, 정렬된 상태로 요소를 유지할 필요가 있을 때만 TreeMap을 사용하는 게 좋다. 그리고 Map은 크기가 성능을 좌우하므로 할당된 크기에 너무 많은 요소가 저장되지 않도록 하는 것이 중요하다

 정렬

정렬에는 sorted(), sortedBy(), sortedDescending(), sortedDescendingBy()가 있다

정리

코틀린의 Collection인 Map, Set, List에 대해서 알아보았습니다. 자바와 다른 것은 Mutable과 Immutable을 구분하여 지원한다는 점입니다. 그 외의 지원하는 메소드는 자바와 거의 유사합니다.

 

'Kotlin > Collection' 카테고리의 다른 글

Collection Sort  (0) 2022.09.12

+ Recent posts