알고리즘 5+1

Kotlin diary (2) - 스트림 함수들

일반정보 2021. 6. 16. 19:34

들어가는 글

이번시간에는 저번에 이어서 kotlin의 스트림 함수들의 대해서 알아보려고 합니다.  스트림함수는 여러 언어에서도 볼 수 있지만, 기분탓인지는 몰라도 kotlin은 다른 언어의 비해 스트림함수가 재미있고, 종류도 많으며, 또 사용하기 편한것이 많은 것 같습니다.  

kotlin에서 다룰 수 있는 스트림 함수를 10가지 주제로 나누어서 정리하였습니다.

예제 코드는 https://play.kotlinlang.org/ 에서 확인해 보실 수 있습니다.

 

1. 단순 반복(foreach)

가장 기본적인 것부터 살펴봅시다.

우선 간단하게 단어가 담긴 'numbers' 라는 리스트를 생성하였습니다. 리스트의 경우는 캐스팅할 수 있는 toList()라는 메서드가 있습니다. 이가 유용하게 사용될 때는 간단하게 배열의 값을 조회할 때인데, 저는 굳이 deepToString()을 쓰는것 보다 편하다고 생각했기 때문입니다. 

  • foreach() -  우리가 쉽게 생각하지만 스트림 함수입니다. 컬렉션의 내용만큼 반복하고 요소에 접근하는 스트림 함수 입니다. 이렇게 내용을 조회할때나 단순 반복에서 많이 사용됩니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)
    
    	println(numbers) //[Apple, Banana, Car, Car]
    	numbers.forEach { print("$it ") } // Apple Banana Car Car  
      	println()
    
}

 


2.  map계열

map 계열의 스트림 함수입니다.

  • map() - 기본적으로 크게 2가지 특징을 가지고 있습니다. 첫번째로 단순 요소를 순회하면서 지정된 함수로 요소를 처리합니다. 두번째로 리스트를 반환합니다. 
  • mapIndexed() -  map에서 순회시 요소의 index 값을 받아올 수 있습니다. 자바스크립트에서는 map이든 reduce든 하나로 통일되어 있었는데, kotlin에서는 이런식으로 Indexed로 나뉜 함수로 나누어져 있습니다. 
  • flatMap -  단일 리스트를 만들어 반환합니다. 예시에서 보듯이 각 단어들은 하나의 String 요소이지만, 그 각 요소는 char로 이루어진 2차원 컬렉션입니다. 따라서 이러한 부분까지 고려되어 단일 리스트로 반환됩니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.map{it + "!"}) // [Apple!, Banana!, Car!, Car!]
    	println(numbers.mapIndexed{ idx, it -> "$idx -> $it!" }) // [0 -> Apple!, 1 -> Banana!, 2 -> Car!, 3 -> Car!]
    	println(numbers.flatMap{it -> it.split("") }) // [, A, p, p, l, e, , , B, a, n, a, n, a, , , C, a, r, , , C, a, r, ]
}

 


3.  조건 관련

조건의 따른 결과만를 반환하는 경우 입니다.

  • filter() - 리스트에서 조건에 맞는것을 찾아야 하는 경우가 있습니다. 이러한 경우에는 간단하게 filter()를 사용할 수 있습니다. 아레예제의 함수안의 논리식은 참/거짓을 반환할 것이며, 참인 경우의 요소들을 반환해 줍니다. 
  • mapNotNull() - map계열이지만 이를 통해 filter처럼 사용할 수 있습니다.  mapNotNull는 요소중 Null이 아닌 요소들만 리스트로 반환합니다. 아래 예제처럼 사용한다면 filter의 효과를 낼 수 있습니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)
	println(numbers.filter{ it -> it.length>5}) // [Banana]
	println(numbers.mapNotNull{ it -> if(it.length>5) it else null}) // [Banana]
}

 


4.  누산 관련

누산기(accumulator) 를 사용하여 하나의 값을 반환하는 경우 입니다. ( joinToString() 는 유사해서 넣음 )

  • joinToString() - 대표적인 리스트를 하나의 문자열로 합쳐 반환해주는 함수입니다. 상당히 많이 사용했었습니다. 
  • reduce() - 누산기(acc)을 사용합니다. 요소를 순회한다고 할때, 첫번째 요소의 결과 값을 누산기(acc)에 적용할 수 있습니다. 이후 두번째 세번째... 마지막 요소까지 처리하면서 누산기에 적용, 반환시킵니다.
  • reduceRight() - reduce와 동일합니다. 다른점이 있다면 거꾸로 순회합니다. 이를 통해 요소의 순서를 거꾸로 할 수도 있습니다. 
  • fold() - 이 또한 reduce와 동일합니다. 다른 점이 있다면 누산기 초기값을 지정할 수 있다는 것입니다. 예제에서는 "numbers : " 라는 문자열을 초기값으로 지정하였습니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.joinToString("+")) // Apple+Banana+Car+Car
    	println(numbers.reduce{acc ,it -> "$acc+$it"}) // Apple+Banana+Car+Car
    	println(numbers.reduceRight { it, acc -> "$acc+$it" }) // Car+Car+Banana+Apple
    	println(numbers.fold("numbers : ") { acc ,it -> "$acc+$it" }) // numbers : +Apple+Banana+Car+Car
}

 


5.  Map 관련

아래 예제들은 모두 Map형식을 반환합니다. 키와 값을 가진 상태가 됩니다. 

위의 2줄은 map() 함수를 사용해서 Piar를 만들고 이를 Map 형식을 만드는 방법입니다.  각각 키를 인덱스와 자기자신인  Map을 만들었습니다. 

  • associateBy() - 이를 통해 간단히 Map을 만들 수 있습니다. 설정된 기준의 따른 키로 Map이 생성됩니다.  아래에서는 각 요소의 문자열 길이를 통해 키를 생성하고 있습니다. 
  • groupBy() - 조건과 키를 지정해서 요소를 분류하고, Map을 반환하고 있습니다. 아래 예제에서는 조건을 '길이가 5 이상인 것'으로 하고 있고, 이의 따라 분류해서 Map을 만들고 있습니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.mapIndexed{ idx, it -> idx to it }.toMap()) // {0=Apple, 1=Banana, 2=Car, 3=Car}
    	println(numbers.map{ it -> it to it }.toMap()) // {Apple=Apple, Banana=Banana, Car=Car}
    	println(numbers.associateBy{ it -> it.length }) // {5=Apple, 6=Banana, 3=Car}
	println(numbers.groupBy{ it -> if(it.length>5) "over-5" else "less-5" }) // {less-5=[Apple, Car, Car], over-5=[Banana]}

}

 


6.  중복 관련

  • distinct() - 기본적으로 중복된 요소를 제거란 리스트를 반환합니다. Set과 비슷한 면이 많습니다. 

아래 에제를 보면, numbers는 "car"라는 중복된 요소를 포합합니다. 이를 toSet()을 이용해서 Set으로 바꾸거나, .distinct()를 이용해서 중복된 요소를 제거할 수 있습니다. 

중복되면 제거된다는 속성을 이용할 수도 있는데 2번째 문단에서는 ["Banana","Car"]의 Set을 아래처럼 빼주는 것으로 마스킹하는 효과를 줄 수 있습니다.

  • distinctBy() - 중복요소를 지정할 수도 있는데 마지막 줄에서 글자수가 같은 것을 제거하면서 같은 문자 길이의  "Car"가 중복이 제거되는 것을 알 수 있습니다. 만약 중복된 요소가 있다면 맨 처음것을 가져갑니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.toSet()) // [Apple, Banana, Car]
    	println(numbers.distinct()) // [Apple, Banana, Car]
    
    	println(numbers.toSet() - listOf("Banana","Car").toSet()) // [Apple]
      	println(numbers.distinctBy{it -> it == "Banana" && it == "Car"}) // [Apple]
    
    	println(numbers.distinctBy{it -> it.length}) // [Apple, Banana, Car]
    
}

 


7. 정렬 관련

kotlion은 기본적으로 sorted()와 sortedDescending()를 이용해서 정렬을 합니다. 

  • sorted() - 오름차순 정렬을 수행합니다. 
  • sortedDescending() - 내림차순 정렬을 수행합니다. 

스트림 함수라는 점을 활용하여, 조건을 따른 정렬을 수행하기 용이합니다. 예제에서는 문자열의 길이의 따라서 정렬을 수행하고 있습니다. (2번째 문단)

fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.sorted()) // [Apple, Banana, Car, Car]
    	println(numbers.sortedDescending()) // [Car, Car, Banana, Apple]
    
    	println(numbers.sortedBy{it -> it.length}) // [Car, Car, Apple, Banana]
    	println(numbers.sortedByDescending{it -> it.length}) // [Banana, Apple, Car, Car]
}

 


8. 제거 관련

아래 예제들은 모두 요소의 제거의 관련된 함수입니다. 

  • drop() - 처음부터 N개의 요소를 제거합니다. 
  • dropLast() - 마지막 부터 N개의 요소를 제거합니다. 
  • dropWhile() - 처음부터 조건에 맞는 요소를 제거합니다. 
  • dropLastWhile() - 마지막 부터 조건에 맞는 요소를 제거합니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.drop(1)) // [Banana, Car, Car]
    	println(numbers.dropLast(1)) // [Apple, Banana, Car]
        
    	println(numbers.dropWhile{it -> it.length < 4}) // [Apple, Banana, Car, Car] 
    	println(numbers.dropLastWhile{it -> it.length < 4}) // [Apple, Banana]
    
}

 


9. Any, All, None

위의 것들은 거의 비슷한 성격을 가지고 있습니다. 모두 함수내의 조건의 따라서, 최종적으로 Boolean값을 반환합니다.

  • any() - 하나라도 요소가 조건을 만족하면 Ture를 그렇지 않으면 False를 반환합니다. 
  • all() - 모든 요소가 같은 조건을 만족하면 Ture를 그렇지 않으면 False를 반환합니다. 
  • none() - 모든 요소가 같은 조건을 만족하면 False를 그렇지 않으면 Ture를 반환합니다. (All의 역입니다)
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.any{it -> it.length>5}) // true
    	println(numbers.all{it -> it.length>2}) // true
    	println(numbers.none{it -> it.length>6}) // true
	
}

 


10. first , last

처음 또는 마지막의 요소를 반환합니다. 

  • first() - 처음 요소, 또는 조건의 맞는 처음 요소를 반환합니다. 
  • last() - 마지막 요소, 또는 조건의 맞는 마지막 요소를 반환합니다. 
fun main() {
	val numbers = listOf("Apple","Banana","Car","Car",)

	println(numbers.first()) // Apple
    	println(numbers.last()) // Car
        
    	println(numbers.first{it.length < 4}) // Car
    	println(numbers.last{it.length > 4}) // Banana
}

 

 

참고 : 

https://junghun0.github.io/2019/08/02/kotlin-stream/