정상에서 IT를 외치다

[스트림 연습] Kotlin Collection Example 본문

안드로이드

[스트림 연습] Kotlin Collection Example

Black-Jin 2019. 6. 25. 11:40
반응형

안녕하세요. 블랙진입니다.

스트림 연습 포스팅입니다.



변환(map, groupby)


Map

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

T타입의 이터레이터를 받아 R타입의 리스트로 반한한다.


val cities = listOf("Seoul","Tokyo","MountainView")

// 1. map에는 함수를 넣을 수 있다. 함수는 { } 안에 -> 를 통해 왼쪽과 오른쪽 인자로 구분된다.
cities.map({ str:String -> str.toUpperCase() }).forEach { print(it) }

// 2. ()안에 인자로 함수 1개만 있는 경우 { } 는 밖으로 나올 수 있다.
cities.map() { str:String -> str.toUpperCase() }.forEach { print(it) }

// 3. (){ }에서 ()는 생략할 수 있다.
cities.map { str:String -> str.toUpperCase() }.forEach { print(it) }

// 4. 스트림 함수에서 기본적으로 반환되는 인자는 it이다.
cities.map { it:String -> it.toUpperCase() }.forEach { print(it) }

// 5. it 인자 1개만 있는 경우 생략이 가능하다
cities.map { it.toUpperCase() }.forEach { print(it) }

// 6. 위와 같은 경우 메소드 레퍼런스로도 표현이 가능하다.
cities.map(String::toUpperCase).forEach { print(it) }



Groupby

public inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>> {
return groupByTo(LinkedHashMap<K, MutableList<T>>(), keySelector)
}

T타입의 이터레이터를 받아 K타입의 키와 T타입 리스트의 값으로 변환한다.



val keySelector = { city: String -> if(city.length > 5) "A" else "B"}

// 함수를 인자로 넘겨줍니다.
cities.groupBy(keySelector)
.forEach { (key, cities) ->
println("$key : $cities")
}

// 위 생략 과정처럼 아래와 같이 표현할 수 있습니다.
cities.groupBy { if(it.length > 5) "A" else "B" }
.forEach { (key, cities) ->
println("$key : $cities")
}

forEach에서 구문분해 구문을 사용해 for을 표현할 수 있습니다.





필터(filter, take, drop, first, last, distinct)


filter

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}

T타입의 이터레이터를 받아 조건에 부합한 값만 다시 T타입의 리스트로 만들어 줍니다.


cities.filter { it.length <= 5 }.forEach { println(it) }



take

public fun <T> Iterable<T>.take(n: Int): List<T> {
require(n >= 0) { "Requested element count $n is less than zero." }
if (n == 0) return emptyList()
if (this is Collection<T>) {
if (n >= size) return toList()
if (n == 1) return listOf(first())
}
var count = 0
val list = ArrayList<T>(n)
for (item in this) {
if (count++ == n)
break
list.add(item)
}
return list.optimizeReadOnlyList()
}

1. take 인자가 0 이상인지 체크합니다.

2. 수신객체가 Collection 인지 확인합니다.

3. 수신객체가 비어있는지 확인합니다.

4. 새로운 ArrayList를 만든 후 count가 n이 될 때까지 add를 해줍니다.


cities.take(1).forEach { println(it) }
cities.takeLast(1).forEach { println(it) }

//첫 인자에서부터 해당 조건을 만족 할 때 까지 배출
cities.takeWhile { it.length > 5 }.forEach { println(it) }



drop

public fun <T> Iterable<T>.drop(n: Int): List<T> {
require(n >= 0) { "Requested element count $n is less than zero." }
if (n == 0) return toList()
val list: ArrayList<T>
if (this is Collection<*>) {
val resultSize = size - n
if (resultSize <= 0)
return emptyList()
if (resultSize == 1)
return listOf(last())
list = ArrayList<T>(resultSize)
if (this is List<T>) {
if (this is RandomAccess) {
for (index in n until size)
list.add(this[index])
} else {
for (item in listIterator(n))
list.add(item)
}
return list
}
}
else {
list = ArrayList<T>()
}
var count = 0
for (item in this) {
if (count++ >= n) list.add(item)
}
return list.optimizeReadOnlyList()
}

1. drop 인자가 0 이상인지 체크합니다.

2. 수신객체가 Collection 인지 확인합니다.

3. 수신객체가 비어있는지 확인합니다.

4. 수신객체 크기에서 n을 제외한 사이즈의 ArrayList를 생성합니다.

5. n 부터 수신객체 크기까지의 데이터만 add를 해줍니다.


cities.drop(1).forEach { println(it) }

//첫 인자에서부터 해당 조건을 만족 할 때 까지 제외
cities.dropWhile { it.length > 5 }.forEach { println(it) }



first

public fun <T> List<T>.first(): T {
if (isEmpty())
throw NoSuchElementException("List is empty.")
return this[0]
}

수신객체가 비어 있지 않으면 첫 번째 인자만 반환합니다.

println(cities.first())



public inline fun <T> Iterable<T>.first(predicate: (T) -> Boolean): T {
for (element in this) if (predicate(element)) return element
throw NoSuchElementException("Collection contains no element matching the predicate.")
}

조건을 만족하는 첫 번째 인자만 반환합니다. 조건에 만족하는 값이 없으면 예외를 던집니다.

println(cities.first { it.length > 5 })



last

public fun <T> List<T>.last(): T {
if (isEmpty())
throw NoSuchElementException("List is empty.")
return this[lastIndex]
}

수신객체가 비어 있지 않으면 마지막 인자만 반환합니다.

println(cities.last())



public inline fun <T> Iterable<T>.last(predicate: (T) -> Boolean): T {
var last: T? = null
var found = false
for (element in this) {
if (predicate(element)) {
last = element
found = true
}
}
if (!found) throw NoSuchElementException("Collection contains no element matching the predicate.")
@Suppress("UNCHECKED_CAST")
return last as T
}

조건을 만족하는 마지막 인자만 반환합니다. 조건에 만족하는 값이 없으면 예외를 던집니다.



distinct

public fun <T> Iterable<T>.distinct(): List<T> {
return this.toMutableSet().toList()
}

수신객체를 MutableSet으로 변환 후 다시 리스트로 바꿔 줍니다. 참고로 list를 set으로 변환하면 중복데이터는 제거됩니다.

val citiesForDistinct = listOf("Seoul","Tokyo","Mountain View","Seoul","Tokyo")

citiesForDistinct.distinct().forEach { println(it) }



distinctBy

public inline fun <T, K> Iterable<T>.distinctBy(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (e in this) {
val key = selector(e)
if (set.add(key))
list.add(e)
}
return list
}

1. 수신객체의 각 아이템에 selector값을 key로 뽑습니다.

2. HashSet에 key를 add 해주는데 중복되어 있는 값이면 false를 반환합니다.

3. HashSet에 add 했을 때 true를 반환하는 값만 list에 추가 후 반환합니다.

val citiesForDistinct = listOf("Seoul","Tokyo","Mountain View","Seoul","Tokyo")

//해당 조건에 맞는 중복값을 제거합니다.
citiesForDistinct.distinctBy { it.length }.forEach { println(it) }

결과 [ Seoul, Mountain View] 




조합 및 합계(zip, joinToString(), count, reduce, fold)


zip

public infix fun <T, R> Iterable<T>.zip(other: Iterable<R>): List<Pair<T, R>> {
return zip(other) { t1, t2 -> t1 to t2 }
}

두 이터레이터를 받아 Pair<T, R>을 List로 묶어 반환해 줍니다.

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

{ t1, t2 -> t1 to t2 } 에서 to는 infix 함수로 Pair(t1, t2)를 보기 쉽게 t1 to t2 로 바꿔 주는 기능을 합니다. 이 둘은 문법만 다를 뿐 똑같습니다.


val cityCodes = listOf("SEO", "TOK", "MTV", "NYC")
val cityNames = listOf("Seoul","Tokyo","MountainView")

cityCodes.zip(cityNames)
.forEach {
println(it.first + " : " + it.second)
}

결과

- SEO : Seoul

- TOK : Tokyo

- MTV : MountainView



joinToString

public fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}

수신객체의 값을 separator로 구분하여 하나의 String 으로 만들어 줍니다.

println(cities.joinToString())
println(cities.joinToString(separator = "|"))

결과

Seoul, Tokyo, MountainView

Seoul|Tokyo|MountainView



count

public inline fun <T> Collection<T>.count(): Int {
return size
}

수신객체의 사이즈를 반환합니다.

public inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int {
if (this is Collection && isEmpty()) return 0
var count = 0
for (element in this) if (predicate(element)) checkCountOverflow(++count)
return count
}

조건을 추가하여 조건에 맞는 값의 갯수만 반환합니다.

println(cities.count { it.length <= 5 })

결과 [2]



reduce

public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}

1. 수신객체를 이터레이터로 만듭니다.

2. 이터레이터가 비어 있는지 확인 후 비어 있다면 예외를 던집니다.

3. 이터레이터의 첫 번째 인자를 acculator에 넣습니다.

4. 이터레이터에 다음 값이 있다면 operation 함수에 acculator와 다음 값을 넣고 이 둘이 조합된 값을 다시 acculator에 넣어줍니다.

println(cities.reduce { acc, s -> acc })
println(cities.reduce { acc, s -> "$acc $s" })

결과

Seoul

Seoul Tokyo MountainView



fold

public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}

reduce와 비교해서 비어있는지 여부를 체크하지 않아도 됩니다. reduce와 동작은 같으나 fold에서는 초기값을 받아 acculator에 설정해 주기 때문입니다.

println(cities.fold("BlackJIn") { acc, s -> "$acc $s" })

결과 [BlackJIn Seoul Tokyo MountainView]




기타(any, none, max, min, average)


any

public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
if (this is Collection && isEmpty()) return false
for (element in this) if (predicate(element)) return true
return false
}

수신객체에 predicate 조건에 부합하는 값이 1개라도 있으면 true을 반환합니다.

println(cities.any { it.length <= 5 })



none

public inline fun <T> Iterable<T>.none(predicate: (T) -> Boolean): Boolean {
if (this is Collection && isEmpty()) return true
for (element in this) if (predicate(element)) return false
return true
}

수신객체에 predicate 조건에 부합하는 값이 1개라도 있으면 false을 반환합니다.

println(cities.none { it.isEmpty() })



max

public fun <T : Comparable<T>> Iterable<T>.max(): T? {
val iterator = iterator()
if (!iterator.hasNext()) return null
var max = iterator.next()
while (iterator.hasNext()) {
val e = iterator.next()
if (max < e) max = e
}
return max
}

가장 큰값 반환


min

public fun <T : Comparable<T>> Iterable<T>.min(): T? {
val iterator = iterator()
if (!iterator.hasNext()) return null
var min = iterator.next()
while (iterator.hasNext()) {
val e = iterator.next()
if (min > e) min = e
}
return min
}

가장 작은값 반환


average

public fun Iterable<Int>.average(): Double {
var sum: Double = 0.0
var count: Int = 0
for (element in this) {
sum += element
checkCountOverflow(++count)
}
return if (count == 0) Double.NaN else sum / count
}

평균 값 반환

val numbers = listOf(4,6,8,21,9,10,2,0)
println(numbers.max())
println(numbers.min())
println(numbers.average())

결과

max : 21

min : 0

average : 7.5

반응형
Comments