groupingBy
groupingBy()
를 이용하면 데이터들을 그룹화 시키는 Grouping 객체를 만들 수 있습니다.
Grouping 클래스에는 그룹화 되어있는 데이터들에 대한 연산을 수행하는 메소드들을 가지고 있습니다
groupingBy()
는 4가지 클래스에서 사용할 수 있습니다.
- Iterable.groupingBy()
- Array.groupingBy()
- Sequence.groupingBy()
- CharSequence.groupingBy()
Grouping의 확장함수
aggregate() 함수
aggregate()
는 Grouping의 인자들을 key로 그룹화 하고 각 그룹의 요소에 순차적으로 연산(operation)을 적용하고 누적값(accumulator) 결과를 Map에 저장합니다.
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.aggregate(
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): Map<K, R> {
return aggregateTo(mutableMapOf<K, R>(), operation)
}
내부적으로 aggregateTo()를 사용하고 있습니다.
aggregateTo() 함수
aggregateTo()
는 각각의 Grouping 인자들을 탐색하여 누적값(accumulator)에 opeartion()를 적용한 결과를 반환합니다.
@SinceKotlin("1.1")
public inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.aggregateTo(
destination: M,
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): M {
for (e in this.sourceIterator()) {
val key = keyOf(e)
val accumulator = destination[key]
destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key))
}
return destination
}
예시
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `aggregate() 테스트`() {
val aggregated = grouping
.aggregate { key, accumulator: Int?, element, first ->
if (first) element else accumulator?.plus(element)
}
println(aggregated) // {0=9, 1=12, 2=15}
}
@Test
fun `aggregate() 테스트2`() {
val aggregated = grouping.aggregate { key, accumulator: StringBuilder?, element, first ->
if (first)
StringBuilder().append(key).append(":").append(element)
else
accumulator!!.append("-").append(element)
}
println(aggregated.values) // [0:0-3-6, 1:1-4-7, 2:2-5-8]
}
}
fold() 함수
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.fold(
initialValue: R,
operation: (accumulator: R, element: T) -> R
): Map<K, R> =
@Suppress("UNCHECKED_CAST")
aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) }
Grouping의 fold()
는 내부적으로 aggregate를 사용하고 있고 첫번째 요소면 initialValue를 accumulator로 넣어주도록 되어있습니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `reduce() 테스트`() {
val reduce = grouping.reduce { _, accumulator, element ->
accumulator + element
}
println(reduce) // {0=9, 1=12, 2=15}
}
}
reduce() 함수
@SinceKotlin("1.1")
public inline fun <S, T : S, K> Grouping<T, K>.reduce(
operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S> =
aggregate { key, acc, e, first ->
@Suppress("UNCHECKED_CAST")
if (first) e else operation(key, acc as S, e)
}
Grouping의 reduce()
도 마찬가지로 내부적으로 aggrgate()를 사용하고 있고, 첫번째 요소일 경우에는 element를 인자로 넣어줍니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `reduce() 테스트`() {
val reduce = grouping.reduce { _, accumulator, element ->
accumulator + element
}
println(reduce) // {0=9, 1=12, 2=15}
}
}
eachCount() 함수
@SinceKotlin("1.1")
public actual fun <T, K> Grouping<T, K>.eachCount(): Map<K, Int> =
// fold(0) { acc, e -> acc + 1 } optimized for boxing
foldTo(destination = mutableMapOf(),
initialValueSelector = { _, _ -> kotlin.jvm.internal.Ref.IntRef() },
operation = { _, acc, _ -> acc.apply { element += 1 } })
.mapValuesInPlace { it.value.element }
Grouping의 eachCount()
는 opeartion을 수행할 때마다 +1을 수행하므로
Map의 value를 갯수로 설정하게 됩니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `eachCount() 테스트`() {
val eachCount = grouping.eachCount()
println(eachCount) // {0=3, 1=3, 2=3}
}
}
감사합니다.
groupingBy
groupingBy()
를 이용하면 데이터들을 그룹화 시키는 Grouping 객체를 만들 수 있습니다.
Grouping 클래스에는 그룹화 되어있는 데이터들에 대한 연산을 수행하는 메소드들을 가지고 있습니다
groupingBy()
는 4가지 클래스에서 사용할 수 있습니다.
- Iterable.groupingBy()
- Array.groupingBy()
- Sequence.groupingBy()
- CharSequence.groupingBy()
Grouping의 확장함수
aggregate() 함수
aggregate()
는 Grouping의 인자들을 key로 그룹화 하고 각 그룹의 요소에 순차적으로 연산(operation)을 적용하고 누적값(accumulator) 결과를 Map에 저장합니다.
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.aggregate(
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): Map<K, R> {
return aggregateTo(mutableMapOf<K, R>(), operation)
}
내부적으로 aggregateTo()를 사용하고 있습니다.
aggregateTo() 함수
aggregateTo()
는 각각의 Grouping 인자들을 탐색하여 누적값(accumulator)에 opeartion()를 적용한 결과를 반환합니다.
@SinceKotlin("1.1")
public inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.aggregateTo(
destination: M,
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): M {
for (e in this.sourceIterator()) {
val key = keyOf(e)
val accumulator = destination[key]
destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key))
}
return destination
}
예시
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `aggregate() 테스트`() {
val aggregated = grouping
.aggregate { key, accumulator: Int?, element, first ->
if (first) element else accumulator?.plus(element)
}
println(aggregated) // {0=9, 1=12, 2=15}
}
@Test
fun `aggregate() 테스트2`() {
val aggregated = grouping.aggregate { key, accumulator: StringBuilder?, element, first ->
if (first)
StringBuilder().append(key).append(":").append(element)
else
accumulator!!.append("-").append(element)
}
println(aggregated.values) // [0:0-3-6, 1:1-4-7, 2:2-5-8]
}
}
fold() 함수
@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.fold(
initialValue: R,
operation: (accumulator: R, element: T) -> R
): Map<K, R> =
@Suppress("UNCHECKED_CAST")
aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) }
Grouping의 fold()
는 내부적으로 aggregate를 사용하고 있고 첫번째 요소면 initialValue를 accumulator로 넣어주도록 되어있습니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `reduce() 테스트`() {
val reduce = grouping.reduce { _, accumulator, element ->
accumulator + element
}
println(reduce) // {0=9, 1=12, 2=15}
}
}
reduce() 함수
@SinceKotlin("1.1")
public inline fun <S, T : S, K> Grouping<T, K>.reduce(
operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S> =
aggregate { key, acc, e, first ->
@Suppress("UNCHECKED_CAST")
if (first) e else operation(key, acc as S, e)
}
Grouping의 reduce()
도 마찬가지로 내부적으로 aggrgate()를 사용하고 있고, 첫번째 요소일 경우에는 element를 인자로 넣어줍니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `reduce() 테스트`() {
val reduce = grouping.reduce { _, accumulator, element ->
accumulator + element
}
println(reduce) // {0=9, 1=12, 2=15}
}
}
eachCount() 함수
@SinceKotlin("1.1")
public actual fun <T, K> Grouping<T, K>.eachCount(): Map<K, Int> =
// fold(0) { acc, e -> acc + 1 } optimized for boxing
foldTo(destination = mutableMapOf(),
initialValueSelector = { _, _ -> kotlin.jvm.internal.Ref.IntRef() },
operation = { _, acc, _ -> acc.apply { element += 1 } })
.mapValuesInPlace { it.value.element }
Grouping의 eachCount()
는 opeartion을 수행할 때마다 +1을 수행하므로
Map의 value를 갯수로 설정하게 됩니다.
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GroupingByTest {
private lateinit var grouping: Grouping<Int, Int>
@BeforeEach
internal fun setUp() {
val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
grouping = list.groupingBy { it % 3 }
}
@Test
fun `eachCount() 테스트`() {
val eachCount = grouping.eachCount()
println(eachCount) // {0=3, 1=3, 2=3}
}
}
감사합니다.