Hi, this might look like a noob question, I'm not ...
# arrow
k
Hi, this might look like a noob question, I'm not even sure it isn't doable with the kotlin standard library… I'm looking to do a "simple" addition to a list, here's a simplified version of what I want:
Copy code
data class Item(val name: String, val quantity: Int)
fun List<Item>.addItem(item: Item): List<Item> { ??? }
listOf(Item("apple", 3), Item("Pear", 1)).addItem(Item("apple", 2) // -> expected : listOf(Item("apple", 5), Item("Pear", 1))
What would be an FP version of
addItem
?
m
Use `+`:
Copy code
fun List<Item>.addItem(item: Item): List<Item> = this + item
☝️ 1
k
🤔 I guess I was not clear enough, I'm trying to merge the items inside the list by name. If the name already exists, I merge the quantities. If not, I simply add the item to the end of the list
t
Item
seems like a monoid, but you still have to figure out the list lookup part
☝🏼 1
t
is List a hard requirement? might wanna use map instead of list. Either as Map<ItemName, Quantity> or Map<ItemName, Item>. your lookup would be trivial (and mostly O(1))
m
Maybe something like this if the function really has to take in and return lists:
Copy code
fun List<Item>.combineItems(): List<Item> {
        val listOfMaps = this.map { mapOf(it.name to it.quantity).k() }
        val combinedMap = MapK.monoid<String, Int>(Int.monoid()).run {
            listOfMaps.combineAll()
        }
        return combinedMap.entries.map { Item(it.key, it.value) }
    }

    fun List<Item>.addItem(item: Item): List<Item> =
            (this + item).combineItems()
But that internally converts the list into a map and then back into a list so using a map in the function signature would probably make more sense.
k
Oh, I didn't see your reply here @mikkom, I came up with this (ugly) thing:
Copy code
fun List<Item>.addItem(item: Item) = (associateBy(Item::name).asSequence() + mapOf(item.name to item).asSequence())
        .groupBy({ it.key }, { it.value })
        .mapValues { (_, values) ->
            values.reduce { acc, item ->
                acc.copy(quantity = acc.quantity + item.quantity)
            }
        }.values.toList()
m
I don't know if my solution is the clearest, either. Just wanted to try and play with Arrow since that's the channel we're in 🙂
In this particular case the simpler the better, I'd say.
m
What about something like this?
Copy code
fun <T> List<T>.mapWhere(predicate: (T) -> Boolean, transform: (T) -> T) = 
    map { if(pred(it)) transform(it) else it }
Then you can do this:
Copy code
fun List<Item>.add(item: Item): List<Item> =
    if (find { it.name == item.name } != null) {
        mapWhere({ it.name == item.name }, 
            { it.copy(quantity = it.quantity + item.quantity) })
    } else {
        this + item
    }
k
Definitely seems nicer than mine ^^