Kotlin Array의 메서드와 확장 함수 파헤치기 (4)

김성준·2022년 12월 22일
0

Kotlin

목록 보기
17/17

Kotlin Array의 메서드와 확장 함수 파헤치기 (4)

확장 함수(Extension Functions)

flatMap, flatMapIndexed, flatten

inline fun <T, R> Array<out T>.flatMap(
    transform: (T) -> Iterable<R>
): List<R>

inline fun <T, R> Array<out T>.flatMapIndexed(
    transform: (index: Int, T) -> Iterable<R>
): List<R>

fun <T> Array<out Array<out T>>.flatten(): List<T>
  • flatMap: 원래 배열의 모든 요소에 대해 transform을 적용한 결과를 하나의 리스트에 담아서 반환합니다.

  • flatMapIndexed: flatMap과 동일하지만 transform 함수에 index 인자가 추가되었습니다.

  • flatten: 이중배열을 풀어서 하나의 리스트에 담아서 반환합니다.

fun main() {
    val array = arrayOf("one two three", "four five six")
    val deepArray = arrayOf(arrayOf(1, 2, 3), arrayOf(4, 5, 6), arrayOf(7, 8, 9))

    val flatMap = array.flatMap { it.split(' ') }
    val flatMapIndexed = array.flatMapIndexed { idx, value ->
        if (idx == 0)
            value.split(' ')
        else
            value.toList()
    }
    val flatten = deepArray.flatten()

    println(flatMap) // [one, two, three, four, five, six]
    println(flatMapIndexed) // [one, two, three, f, o, u, r,  , f, i, v, e,  , s, i, x]
    println(flatten) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
}

fold, foldIndexed, foldRight, foldRightIndexed

inline fun <T, R> Array<out T>.fold(
    initial: R,
    operation: (acc: R, T) -> R
): R

inline fun <T, R> Array<out T>.foldIndexed(
    initial: R,
    operation: (index: Int, acc: R, T) -> R
): R

inline fun <T, R> Array<out T>.foldRight(
    initial: R,
    operation: (T, acc: R) -> R
): R

inline fun <T, R> Array<out T>.foldRightIndexed(
    initial: R,
    operation: (index: Int, T, acc: R) -> R
): R
  • fold: initial이 acc의 초기값이 되고, 처음부터 마지막 요소까지 operation 함수가 반복되며 그 결과가 acc로 대입됩니다. 마지막까지 순회한 후의 acc가 fold의 최종 반환값이 됩니다.

  • foldIndexed: fold와 같으며, operation 함수에 index 인자가 추가되었습니다.

  • foldRight: fold와 같으며, 마지막에서 처음까지의 순서로 순회합니다.

  • foldRightIndexed: foldIndexed와 같으며, 마지막에서 처음까지의 순서로 순회합니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)

    val fold = array.fold(0) { acc, value ->
        print("value: $value ")
        acc + value }

    val foldIndexed = array.foldIndexed(0) { idx, acc, value ->
        println("idx: $idx value: $value acc: $acc")
        if (idx > 2)
            acc + value
        else
            acc
    }

    val foldRight = array.foldRight(0) { acc, value ->
        println("value: $acc")
        acc + value
    }

    val foldRightIndexed = array.foldRightIndexed(0) { idx, value, acc ->
        println("idx: $idx acc: $acc value: $value")
        if (idx > 2)
            acc + value
        else
            acc
    }

    println(fold) // 1 + 2 + 3 + 4 + 5 + 6 = 21
    println(foldIndexed) // 4 + 5 + 6 = 15
    println(foldRight) // 6 + 5 + 4 + 3 + 2 + 1 = 21
    println(foldRightIndexed) // 6 + 5 + 4 = 15
}

fold와 foldRight는 value와 acc의 순서가 역순이니 주의해서 사용합시다!

inline fun <T, R> Array<out T>.fold(
    initial: R,
    operation: (acc: R, T) -> R
): R
inline fun <T, R> Array<out T>.foldRight(
    initial: R,
    operation: (T, acc: R) -> R
): R

forEach, forEachIndexed

public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit 

inline fun <T> Array<out T>.forEachIndexed(
    action: (index: Int, T) -> Unit)
  • forEach: 각 요소에 action을 수행합니다.

  • forEachIndexed: forEach와 동일하지만, action에 index 인자가 있습니다.

forEach는 각 요소에 대해서 action을 수행하지만 그 결과가 반환되지는 않습니다. 반환값이 Unit이라는 사실을 인지하고 사용해야합니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)

    array.forEach {
        print("$it ") // 1 2 3 4 5 6 
    }

    array.forEachIndexed { index, value ->
        println("index: $index, value: $value")
//        index: 1, value: 2
//        index: 2, value: 3
//        index: 3, value: 4
//        index: 4, value: 5
//        index: 5, value: 6
    }
}

onEach, onEachIndexed

inline fun <T> Array<out T>.onEach(
    action: (T) -> Unit
): Array<out T>

inline fun <T> Array<out T>.onEachIndexed(
    action: (index: Int, T) -> Unit
): Array<out T>

onEach: 배열의 요소를 순회하며 action을 수행합니다. 그 후에 원본 배열을 반환합니다.

onEachIndexed: onEach와 동일하나, action에 index 인자가 있습니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)

    val onEach = array.onEach { println(it) }

    println("onEach: ${onEach.contentToString()}") 
    // onEach: [1, 2, 3, 4, 5, 6]

    val onEachIndexed = array.onEachIndexed { index, value ->
        if (index > 2)
            array[index] += 1
    }

    println("onEachIndexed: ${onEachIndexed.contentToString()}") 
    // onEachIndexed: [1, 2, 3, 5, 6, 7]
}

getOrElse, getOrNull

public inline fun <T> Array<out T>
	.getOrElse(index: Int, defaultValue: (Int) -> T): T

fun <T> Array<out T>.getOrNull(index: Int): T?
  • getOrElse: 해당 인덱스에 해당하는 배열의 요소를 반환합니다. 만약, 인덱스가 배열의 범위를 넘어가면 defaultValue를 반환합니다.

  • getOrNull: getOrElse와 동일하지만, 인덱스가 배열의 범위를 넘어갈 경우에 null을 반환합니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)

    val getOrElseInCase = array.getOrElse(3) { "out of bound" }
    val getOrNullInCase = array.getOrNull(3)
    val getOrElseOutCase = array.getOrElse(-1) { "out of bound" }
    val getOrNullOutCase = array.getOrNull(-1)

    println(getOrElseInCase) // 4
    println(getOrNullInCase) // 4
    println(getOrElseOutCase) // out of bound
    println(getOrNullOutCase) // null
}

groupBy, groupByTo, groupingBy

inline fun <T, K> Array<out T>.groupBy(
    keySelector: (T) -> K
): Map<K, List<T>>

inline fun <T, K, V> Array<out T>.groupBy(
    keySelector: (T) -> K,
    valueTransform: (T) -> V
): Map<K, List<V>>

inline fun <T, K, M : MutableMap<in K, MutableList<T>>> Array<out T>.groupByTo(
    destination: M,
    keySelector: (T) -> K
): M

inline fun <T, K, V, M : MutableMap<in K, MutableList<V>>> Array<out T>.groupByTo(
    destination: M,
    keySelector: (T) -> K,
    valueTransform: (T) -> V
): M

inline fun <T, K> Array<out T>.groupingBy(
    crossinline keySelector: (T) -> K
): Grouping<T, K>
  • groupBy: keySelector 함수에 의해 반환되는 키가 동일한 요소끼리 그룹지은 Map을 반환합니다. 반환된 Map은 원본 배열의 순서를 보장합니다.

  • groupByTo: groupBy와 동일하지만, 추가될 Map을 지정할 수 있습니다.

  • groupingBy: keySelector 함수에 의해 반환되는 키가 동일한 요소끼리 묶어 Grouping 객체로 반환합니다. Grouping 객체를 사용하면, 그룹된 요소들의 수를 세거나, 더하는 등의 작업을 쉽게 할 수 있고 불필요한 컬렉션의 생성을 막을 수 있습니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val grouping: Grouping<Int, Int> = array.groupingBy { it % 2 }

    val groupBy = array.groupBy { it % 2 }
    val groupingBy = grouping.fold(0) { acc, value ->
        acc + value
    }

    println(groupBy) // {1=[1, 3, 5], 0=[2, 4, 6]}
    println(groupingBy) // {1=9, 0=12}
}

indexOf, indexOfFirst, indexOfLast

fun <T> Array<out T>.indexOf(element: T): Int

inline fun <T> Array<out T>.indexOfFirst(
    predicate: (T) -> Boolean
): Int

inline fun <T> Array<out T>.indexOfLast(
    predicate: (T) -> Boolean
): Int
  • indexOf: 배열에서 함수의 element 인자와 일치하는 요소의 인덱스를 반환합니다. 만약 일치하는 요소가 존재하지 않는다면 -1을 반환합니다.

  • indexOfFirst: 배열에서 predicate가 true를 반환하는 첫번째 요소를 반환합니다. 일치하는 요소가 존재하지 않으면 -1을 반환합니다.

  • indexOfLast: indexOfFirst와 동일하지만, 배열을 역순으로 순회합니다.

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)

    val indexOf = array.indexOf(3)
    val indexOfFirst = array.indexOfFirst {
        print("$it ") // 1 2 3
        it == 3
    }.also { println() }
    val indexOfLast = array.indexOfLast {
        print("$it ") // 6 5 4 3
        it == 3
    }.also { println() }

    println(indexOf) // 2
    println(indexOfFirst) // 2
    println(indexOfLast) // 2
}

intersect

infix fun <T> Array<out T>.intersect(
    other: Iterable<T>
): Set<T>

intersect 함수의 인자로 들어온 컬렉션과 원본 배열에 둘 다 존재하는 인자들을 담은 Set을 반환합니다. (교집합)

반환된 Set은 원본 배열의 순서를 보장합니다.

또한 intersect는 중위함수(infix)로 선언되어있으므로 중위표현으로 함수호출이 가능합니다.
(array intersect argument)

fun main() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val list = listOf(2, 4, 6)

    val intersect = array.intersect(list)
    val infixIntersect = array intersect list

    println(intersect) // [2, 4, 6]
    println(infixIntersect) // [2, 4, 6]
}

isArrayOf, isEmpty, isNotEmpty, isNullOrEmpty

fun <reified T : Any> Array<*>.isArrayOf(): Boolean

fun <T> Array<out T>.isEmpty(): Boolean

fun <T> Array<out T>.isNotEmpty(): Boolean

fun Array<*>?.isNullOrEmpty(): Boolean
  • isArrayOf: 해당 타입의 배열인지 아닌지를 판별합니다.
  • isEmpty: 배열이 비어있는지 아닌지 판별합니다.
  • isNotEmpty: isEmpty의 역입니다.
  • isNullOrEmpty: 배열이 널이거나 비어있는지 판별합니다.
fun main() {
    val array: Array<Int> = arrayOf(1, 2, 3, 4, 5, 6)
    val nullArray: Array<Int>? = null
    val emptyArray: Array<Int> = arrayOf()

    val isArrayOfTrue = array.isArrayOf<Int>()
    val isArrayOfFalse = array.isArrayOf<Char>()
    val isEmptyTrue = emptyArray.isEmpty()
    val isEmptyFalse = array.isEmpty()
    val isNullOrEmptyTrue = nullArray.isNullOrEmpty()
    val isNullOrEmptyFalse = array.isNullOrEmpty()

    println(isArrayOfTrue) // true
    println(isArrayOfFalse) // false
    println(isEmptyTrue) // true
    println(isEmptyFalse) // false
    println(isNullOrEmptyTrue) // true
    println(isNullOrEmptyFalse) // false
}

joinTo, joinToString

fun <T, A : Appendable> Array<out T>.joinTo(
    buffer: A,
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = "",
    limit: Int = -1,
    truncated: CharSequence = "...",
    transform: ((T) -> CharSequence)? = null
): A

fun <T> Array<out T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = "",
    limit: Int = -1,
    truncated: CharSequence = "...",
    transform: ((T) -> CharSequence)? = null
): String

모든 요소를 seperator 인자로 구분하고 prefix와 postfix를 앞 뒤에 붙인 문자열반환합니다.

만약 컬렉션의 크기가 크다면, limit 인자를 사용하여 몇 개의 인자만 표시할 것인지 결정할 수 있습니다. 그럼 해당하는 갯수만큼만 표시되고 이후에 truncated 인자에 해당하는 문자열이 이어붙습니다.

transform 인자를 사용하면 배열의 각 요소에 변화를 준 후 문자열로 전환할 수 있습니다.

  • joinTo: buffer 인자를 사용하여 반환된 문자열이 추가될 버퍼를 지정할 수 있습니다.

  • joinToString: 단순히 변환된 문자열만을 반환합니다.

fun main() {
    val array = Array<Int>(100) { it + 1 }
    val buffer = StringBuilder("this is Buffer: ")

    val joinTo = array.joinTo(
        buffer = buffer,
        separator = ". ",
        prefix = "[",
        postfix = "]",
        limit = 10,
        truncated = "... more"
    ) {
        (it + 1).toString()
    }

    val joinToString = array.joinToString(
        separator = ". ",
        prefix = "[",
        postfix = "]",
        limit = 10,
        truncated = "... more") {
        (it + 1).toString()
    }

    println(joinTo)
    // this is Buffer: [2. 3. 4. 5. 6. 7. 8. 9. 10. 11. ... more]
    println(joinToString)
    // [2. 3. 4. 5. 6. 7. 8. 9. 10. 11. ... more]
}

last, lastIndexOf, lastOrNull

fun <T> Array<out T>.last(): T

inline fun <T> Array<out T>.last(
    predicate: (T) -> Boolean
): T

fun <T> Array<out T>.lastIndexOf(element: T): Int

fun <T> Array<out T>.lastOrNull(): T?

inline fun <T> Array<out T>.lastOrNull(
    predicate: (T) -> Boolean
): T?
  • last(): 배열의 마지막 요소를 반환합니다. 배열이 비어있다면, NoSuchElementException이 발생합니다.

  • last(predicate): predicate가 true를 반환하는 마지막 요소를 반환합니다. 일치하는 요소가 없다면 NoSuchElementException이 발생합니다.

  • lastIndexOf: 배열에서 elemnet의 인자와 일치하는 마지막 요소의 인덱스를 반환합니다. 존재하지 않으면 -1을 반환합니다.

  • lastOrNull: last와 동일하지만 예외가 발생하지 않고, 예외 상황에 null을 반환합니다.

map, mapIndexed, mapIndexedNotNull, mapNotNull, ~To

inline fun <T, R> Array<out T>.map(
    transform: (T) -> R
): List<R>

inline fun <T, R> Array<out T>.mapIndexed(
    transform: (index: Int, T) -> R
): List<R>

inline fun <T, R : Any> Array<out T>.mapNotNull(
    transform: (T) -> R?
): List<R>

inline fun <T, R : Any> Array<out T>.mapIndexedNotNull(
    transform: (index: Int, T) -> R?
): List<R>

inline fun <T, R, C : MutableCollection<in R>> Array<out T>.mapTo(
    destination: C,
    transform: (T) -> R
): C

inline fun <T, R, C : MutableCollection<in R>> Array<out T>.mapIndexedTo(
    destination: C,
    transform: (index: Int, T) -> R
): C

inline fun <T, R : Any, C : MutableCollection<in R>> Array<out T>.mapNotNullTo(
    destination: C,
    transform: (T) -> R?
): C

inline fun <T, R : Any, C : MutableCollection<in R>> Array<out T>.mapIndexedNotNullTo(
    destination: C,
    transform: (index: Int, T) -> R?
): C
  • map: 배열의 각 요소에 transform을 적용한 결과가 담긴 리스트를 반환합니다.

  • mapIndexed: map과 동일하나 transform에 index 인자가 존재합니다.

  • mapNotNull: 배열의 각 요소에 transform을 적용한 결과가 null이 아닌 요소들만 담긴 리스트를 반환합니다.

  • mapIndexedNotNull: mapNotNull과 동일하나 transform에 index 인자가 존재합니다.

  • (map...)To: destination으로 결과가 추가될 리스트를 지정할 수 있습니다.

fun main() {
    val array = Array<Int>(6) { it + 1 }
    val stringArray = arrayOf("123", "abc", "2345", "65p", "3245")

    val map = array.map { it.toString() }
    val mapIndexed = array.mapIndexed { idx, value ->
        if (idx % 2 == 0)
            "\"$value\""
        else
            value
    }
    val mapNotNull = stringArray.mapNotNull {
        it.toIntOrNull()
    }
    val mapIndexedNotNull = stringArray.mapIndexedNotNull { idx, value ->
        if (idx < 3) {
            value.toIntOrNull()
        } else {
            value
        }
    }

    println(map) // [1, 2, 3, 4, 5, 6]
    println(mapIndexed) // ["1", 2, "3", 4, "5", 6]
    println(mapNotNull) // [123, 2345, 3245]
    println(mapIndexedNotNull) // [123, 2345, 65p, 3245]
}

maxOf, maxWithOrNull, maxOfWith, maxByOrNull, maxOfOrNull, maxOfWithOrNull

inline fun <T> Array<out T>.maxOf(
    selector: (T) -> Double
): Double

fun <T> Array<out T>.maxWithOrNull(
    comparator: Comparator<in T>
): T?

inline fun <T, R> Array<out T>.maxOfWith(
    comparator: Comparator<in R>,
    selector: (T) -> R
): R

inline fun <T, R : Comparable<R>> Array<out T>.maxByOrNull(
    selector: (T) -> R
): T?
  • maxOf: 배열의 각 요소에 selector를 적용한 후 가장 큰 값을 반환합니다. 그중 한 요소가 NaN을 반환하면 maxOf는 NaN을 반환합니다. 배열이 비어있으면 NoSuchElement 예외가 발생합니다.

  • maxWithOrNull: maxWith가 deprecated되고 이젠 maxWithOrNull을 사용해야합니다. maxWithOrNull은 Comparator를 인자로 받아 그 Comparator를 기준으로 가장 큰 값을 반환합니다.

  • maxOfWith: 배열의 각 요소에 selector를 적용한 후 그 요소들에 대해 comparator를 사용하여 비교합니다. 그 결과 가장 큰 값을 반환합니다. 배열이 비어있다면, NoSuchElement 예외가 발생합니다.

  • maxByOrNull: 배열의 각 요소에 selecor를 적용한 결과가 가장 큰 첫번째 요소를 반환합니다. 배열이 비어있다면 null을 반환합니다.

fun main() {
    val array = arrayOf<Int>(1, 2, 3, 4, 5)
    val pairArray = arrayOf("one" to 1, "two" to 2, "three" to 3)

    val maxOf = array.maxOf { it }
    val maxWith = array.maxWithOrNull { lhs, rhs ->
        lhs - rhs
    }
    val maxOfWith = pairArray.maxOfWith(
        comparator = { lhs, rhs ->
            lhs.second - rhs.second
        },
        selector = { it }
    )
    val maxByOrNull = pairArray.maxByOrNull { it.second }

    println(maxOf) // 5
    println(maxWith) // 5
    println(maxOfWith) // (three, 3)
    println(maxByOrNull) // (three, 3)
}

Comparator?
Comparator는 compare라는 메서드를 단 하나만 가지고 있는 functional interface 입니다. 그렇기 때문에 람다식으로 인자를 전달할 수 있습니다.
(SAM 인터페이스라고도 합니다.)

minOf, minWithOrNull, minOfWith, minByOrNull, minOfOrNull, minOfWithOrNull

inline fun <T> Array<out T>.minOf(
    selector: (T) -> Double
): Double

fun <T> Array<out T>.minWithOrNull(
    comparator: Comparator<in T>
): T?

inline fun <T, R> Array<out T>.minOfWith(
    comparator: Comparator<in R>,
    selector: (T) -> R
): R

inline fun <T, R : Comparable<R>> Array<out T>.minByOrNull(
    selector: (T) -> R
): T?

max와 모두 동일하지만, 최솟값을 찾아줍니다.

fun main() {
    val array = arrayOf<Int>(1, 2, 3, 4, 5)
    val pairArray = arrayOf("one" to 1, "two" to 2, "three" to 3)

    val minOf = array.minOf { it }
    val minWith = array.minWithOrNull { lhs, rhs ->
        lhs - rhs
    }
    val minOfWith = pairArray.minOfWith(
        comparator = { lhs, rhs ->
            lhs.second - rhs.second
        },
        selector = { it }
    )
    val minByOrNull = pairArray.minByOrNull { it.second }

    println(minOf) // 1
    println(minWith) // 1
    println(minOfWith) // (one, 1)
    println(minByOrNull) // (one, 1)
}

none

fun <T> Array<out T>.none(): Boolean

inline fun <T> Array<out T>.none(
    predicate: (T) -> Boolean
): Boolean
  • none(): isEmpty와 같은 동작을 합니다.

  • none(predicate): 배열의 요소 중 predicate를 만족하는 요소가 없다면 true를 반환합니다.

fun main() {
    val array = arrayOf<Int>(1, 2, 3, 4, 5)
    val emptyArray = arrayOf<Int>()

    val noneFalseCondition = array.none()
    val noneTrueCondition = emptyArray.none()
    val predicateNoneFalseCondition = array.none { it % 2 == 0 }
    val predicateNoneTrueCondition = array.none { it > 6 }

    println(noneFalseCondition) // true
    println(noneTrueCondition) // false
    println(predicateNoneTrueCondition) // true
    println(predicateNoneFalseCondition) // false
}

partion

inline fun <T> Array<out T>.partition(
    predicate: (T) -> Boolean
): Pair<List<T>, List<T>>

원본 배열의 요소에 predicate가 true를 반환한 결과와 false를 반환한 결과를 두 개의 리스트에 나누어 담습니다. 이 두 리스트를 Pair로 묶어서 반환합니다.

fun main() {
    val array = arrayOf<Int>(1, 2, 3, 4, 5, 6)

    val partition: Pair<List<Int>, List<Int>> = array.partition { it % 2 == 0 }
    val (evens: List<Int>, odds: List<Int>) = array.partition { it % 2 == 0 }

    println(partition) // ([2, 4, 6], [1, 3, 5])
    println(evens) // [2, 4, 6]
    println(odds) // [1, 3, 5]
}

구조 분해 할당?

random, randomOrNull

fun <T> Array<out T>.random(): T

fun <T> Array<out T>.random(random: Random): T
  • random(): 배열에서 임의의 요소를 반환합니다.

  • random(random): 지정된 난수 소스를 사용하여 배열에서 임의의 요소를 반환합니다.

배열이 비어있으면 NoSuchElementException이 발생하지만, randomOrNull을 사용하면 null을 반환합니다.

fun main() {
    val array = arrayOf<Int>(1, 2, 3, 4, 5, 6)
    val charBuffer = CharArray(10)
    
    File("/dev/random")
        .bufferedReader()
        .read(charBuffer)
    
    val randomSeedList = charBuffer.map { it.code.toLong() }

    repeat(5) {
        println(array.random())
    }

    println()

    repeat(5) {
        println(array.random(Random(randomSeedList[it])))
    }
}

출처

코틀린 공식 문서

profile
수신제가치국평천하

0개의 댓글

관련 채용 정보