관리 메뉴

나만을 위한 블로그

[Kotlin] 컬렉션 필터링 (filter, any, none, all) 본문

개인 공부/Kotlin

[Kotlin] 컬렉션 필터링 (filter, any, none, all)

참깨빵위에참깨빵 2023. 11. 8. 23:30
728x90
반응형

이 포스팅은 아래의 코틀린 공식문서를 바탕으로 작성했다.

 

https://kotlinlang.org/docs/collection-filtering.html

 

Filtering collections | Kotlin

 

kotlinlang.org

 

필터링은 컬렉션 처리에서 가장 많이 사용되는 작업 중 하나다. 코틀린에서 필터링 조건은 조건자라는 컬렉션 요소를 받아서 Boolean 값을 리턴하는 람다 함수로 정의된다. true가 리턴된다면 주어진 요소가 조건자와 일치한다는 뜻이고 false는 일치하지 않는다는 뜻이다.

코틀린 표준 라이브러리에는 한 번의 호출로 컬렉션을 필터링할 수 있는 확장 함수 그룹이 존재한다. 이 함수들은 기존 컬렉션을 바꾸지 않고 유지하므로 가변 컬렉션, 불변 컬렉션 모두에 적용할 수 있다. 필터링한 결과를 연산하려면 필터링 이후 변수에 할당하거나 함수를 연결해야 한다.

 

조건자로 필터링

 

기본적인 필터링 함수는 filter()다. 조건자와 같이 호출하면 일치하는 컬렉션 요소를 리턴한다. List, Set을 넣을 경우 모두 리턴타입이 리스트고 Map은 Map을 리턴타입으로 얻는다.

 

val numbers = listOf("one", "two", "three", "four")  
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3)

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap)

// [three, four]
// {key11=11}

 

filter()의 조건자는 요소의 값만 확인할 수 있다. filter의 요소 위치를 사용하려면 filterIndexed()를 사용하면 된다. filterIndexed()는 인덱스, 요소라는 2가지 매개변수가 있는 조건자를 사용한다. 부정적인 조건으로 컬렉션을 필터링하려면 filterNot()을 사용한다.

 

val numbers = listOf("one", "two", "three", "four")

val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5)  }
val filteredNot = numbers.filterNot { it.length <= 3 }

println(filteredIdx)
println(filteredNot)

// [two, four]
// [three, four]

 

또는 특정 타입의 요소를 필터링해서 요소 타입의 범위를 좁히는 함수도 있다.

filterIsInstance()는 지정된 타입의 컬렉션을 리턴한다. List<Any>에서 호출하면 filterIsInstance<T>()는 List<T>를 리턴하기 때문에 해당 항목에 대해 T 타입의 함수를 호출할 수 있다.

 

val numbers = listOf(null, 1, "two", 3.0, "four")
println("All String elements in upper case:")
numbers.filterIsInstance<String>().forEach {
    println(it.uppercase())
}

// All String elements in upper case:
// TWO
// FOUR

 

filterNotNull()은 null이 아닌 모든 요소를 리턴한다. List<T?>에서 호출되면 filterNotNull()은 List<T: Any>를 리턴하므로 요소를 null을 허용하지 않는 객체로 처리할 수 있다.

 

val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
    println(it.length)   // null이 될 수 있는 문자열에는 length를 쓸 수 없다
}

// 3
// 3

 

분할(Partition)

 

다른 필터링 함수인 partition()은 조건자 기준으로 컬렉션을 필터링하고 불일치하는 요소를 별도 리스트에 유지한다. 따라서 리턴값으로 리스트의 Pair가 있다. 첫 리스트에는 조건자와 일치하는 요소가 포함돼 있고 2번째 리스트에는 원래 컬렉션의 다른 모든 요소들이 포함돼 있다.

 

val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }

println(match)
println(rest)

// [three, four]
// [one, two]

 

테스트 술어(Test predicates)

 

마지막으로 컬렉션 요소에 대해 조건자를 간단히 테스트하는 함수들이 있다.

 

  • any() : 하나 이상의 요소가 주어진 조건과 일치하면 true 리턴
  • none() : 주어진 조건과 일치하는 요소가 없으면 true 리턴
  • all() : 모든 요소가 주어진 조건과 일치하면 true 리턴. 빈 컬렉션에서 유효한 조건자와 같이 호출해도 true를 리턴. 이런 행동은 논리적으로 공허한 진실(vacuous truth)로 알려져 있다.

 

val numbers = listOf("one", "two", "three", "four")

println(numbers.any { it.endsWith("e") })
println(numbers.none { it.endsWith("a") })
println(numbers.all { it.endsWith("e") })

println(emptyList<Int>().all { it > 5 })   // vacuous truth

// true
// true
// false
// true

 

any(), none()은 조건자가 없더라도 쓸 수 있다. 이 경우 컬렉션이 비어 있는지 확인만 한다. any()는 요소가 있으면 true를 리턴하고 없으면 false를 리턴한다. none은 반대로 요소가 있으면 false를 리턴하고 없으면 true를 리턴한다.

 

이 함수들은 잘만 사용하면 리팩토링할 때 코드를 더욱 간결하게 만들 수 있다. 하지만 뭐든 많이 쓰면 안 좋다고, 쓸데없이 많이 사용하면 되려 가독성을 해칠 수 있으니 적절하게 사용하는 게 좋을 것이다.

반응형
Comments