kotlinintermediate

Multimap, Grouping, and Counting Patterns

Build multimaps, aggregate data, count occurrences, and perform grouped reductions in Kotlin.

kotlin
data class LogEntry(
    val timestamp: String,
    val level: String,
    val service: String,
    val message: String
)

data class Sale(
    val product: String,
    val category: String,
    val amount: Double,
    val region: String
)

// Simple multimap
class MultiMap<K, V> {
    private val map = mutableMapOf<K, MutableList<V>>()

    fun put(key: K, value: V) {
        map.getOrPut(key) { mutableListOf() }.add(value)
    }

    fun get(key: K): List<V> = map[key] ?: emptyList()
    fun keys(): Set<K> = map.keys
    fun entries(): Map<K, List<V>> = map.toMap()
    fun flatValues(): List<V> = map.values.flatten()
    fun size(): Int = map.values.sumOf { it.size }
    override fun toString() = map.toString()
}

fun main() {
    // Logs for grouping
    val logs = listOf(
        LogEntry("10:00", "INFO", "auth", "User login"),
        LogEntry("10:01", "ERROR", "api", "Timeout"),
        LogEntry("10:02", "INFO", "auth", "User logout"),
        LogEntry("10:03", "ERROR", "api", "Connection refused"),
        LogEntry("10:04", "WARN", "auth", "Invalid token"),
        LogEntry("10:05", "ERROR", "db", "Deadlock"),
        LogEntry("10:06", "INFO", "api", "Health check")
    )

    // groupBy
    val byLevel = logs.groupBy { it.level }
    println("--- By Level ---")
    byLevel.forEach { (level, entries) ->
        println("$level: ${entries.size} entries")
    }

    // groupingBy + eachCount
    val levelCounts = logs.groupingBy { it.level }.eachCount()
    println("\nCounts: $levelCounts")

    // groupingBy + fold
    val messagesByLevel = logs.groupingBy { it.level }
        .fold(mutableListOf<String>()) { acc, entry ->
            acc.apply { add(entry.message) }
        }
    println("\nMessages by level: $messagesByLevel")

    // groupingBy + reduce
    val latestByService = logs.groupingBy { it.service }
        .reduce { _, acc, element ->
            if (element.timestamp > acc.timestamp) element else acc
        }
    println("\nLatest per service:")
    latestByService.forEach { (svc, entry) -> println("  $svc: ${entry.message}") }

    // Sales data
    val sales = listOf(
        Sale("Laptop", "Electronics", 999.0, "North"),
        Sale("Mouse", "Electronics", 29.0, "South"),
        Sale("Book", "Books", 15.0, "North"),
        Sale("Laptop", "Electronics", 1099.0, "South"),
        Sale("Keyboard", "Electronics", 79.0, "North"),
        Sale("Book", "Books", 25.0, "South"),
        Sale("Laptop", "Electronics", 899.0, "North")
    )

    // Multi-level grouping
    val byCategoryAndRegion = sales
        .groupBy { it.category }
        .mapValues { (_, catSales) ->
            catSales.groupBy { it.region }.mapValues { (_, regionSales) ->
                mapOf(
                    "count" to regionSales.size,
                    "total" to regionSales.sumOf { it.amount },
                    "avg" to regionSales.map { it.amount }.average()
                )
            }
        }
    println("\n--- Sales by Category & Region ---")
    byCategoryAndRegion.forEach { (cat, regions) ->
        println("$cat:")
        regions.forEach { (region, stats) ->
            println("  $region: $stats")
        }
    }

    // Top N
    val topProducts = sales
        .groupBy { it.product }
        .mapValues { (_, s) -> s.sumOf { it.amount } }
        .entries
        .sortedByDescending { it.value }
        .take(3)
    println("\nTop 3 products: ${topProducts.map { "${it.key}: \$${it.value}" }}")

    // MultiMap
    println("\n--- MultiMap ---")
    val mm = MultiMap<String, String>()
    mm.put("fruits", "apple")
    mm.put("fruits", "banana")
    mm.put("fruits", "cherry")
    mm.put("vegs", "carrot")
    mm.put("vegs", "spinach")
    println("Fruits: ${mm.get("fruits")}")
    println("Total items: ${mm.size()}")

    // Word frequency
    val text = "the quick brown fox jumps over the lazy brown fox"
    val frequency = text.split(" ")
        .groupingBy { it }
        .eachCount()
        .entries
        .sortedByDescending { it.value }
    println("\nWord frequency:")
    frequency.forEach { (word, count) -> println("  $word: $count") }

    // Partition (special case of groupBy with 2 groups)
    val (errors, nonErrors) = logs.partition { it.level == "ERROR" }
    println("\nErrors: ${errors.size}, Non-errors: ${nonErrors.size}")
}

Use Cases

  • Log aggregation and analysis
  • Sales data multi-level reporting
  • Word frequency and text analysis

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.