Feedback on Lambdas - kotlin

I was hoping someone could provide me some feedback on better/cleaner ways to do the following:
val driversToIncome = trips
.map { trip ->
// associate every driver to a cost (NOT UNIQUE)
trip.driver to trip.cost }
.groupBy (
// aggregate all costs that belong to a driver
keySelector = { (driver, _) -> driver },
valueTransform = { (_, cost) -> cost }
)
.map { (driver, costs) ->
// sum all costs for each driver
driver to costs.sum() }
.toMap()

You can do it like this:
val driversToIncome = trips
.groupingBy { it.driver }
.fold(0) { acc, trip -> acc + trip.cost }
It groups trips by driver and while grouping it sums costs per each driver separately.
Note that groupingBy() does not do anything on its own, it only prepares for the grouping operation. This solution avoids creating intermediary collections, it does everything in a single loop.
Then fold() calls the provided lambda sequentially on each item belonging to the specific group. Lambda receives a result from the previous call and it provides a new result (result is called accumulator). As a result, it reduces a collection of items to a single item.
You can read more about this kind of transformations in documentation about Grouping and Aggregation. Also, they aren't really inventions of Kotlin. Such operations exist in other languages and data transformation tools, so you can read about it even on Wikipedia.

Related

mapping custom object kotlin

I have a custom object:
data class MoneyTransaction(
val amount: Double,
val category: String
)
I have a list of MoneyTransaction. I want to create a map out of that list where keys are categories, and the values are the total amount according to the category. Kotlin has functions like groupBy, groupByTo, groupingBy. But there is no tutorial or documentation about those, so I can't figure it out. So far I got this:
val map = transactionList.groupBy({it.category},{it.amount})
But this doesn't give the total amount, just separate amounts on each category
Any help would be much appreciated.
So first of all you group your transactions by category
transactionList.groupBy { it.category }
this gives you a Map<String, List<MoneyTransaction>> after that you need to sum up the amounts
transactionList.groupBy { it.category }
.mapValues { (_, transactionsInCategory) ->
transactionsInCategory.sumOf { it.amount }
}
This will give you a Map<String, Double> with the value representing the sum of all transactions in the category.
You can use groupingBy and then fold:
transactions.groupingBy(MoneyTransaction::category)
.fold(0.0) { acc, next -> acc + next.amount }
groupingBy here would return a Grouping<MoneyTransaction, String>, which is an intermediate collection of the groups. Then you fold each of the groups by starting from 0, and adding the next transaction's amount.
Looking at the implementation, the groupingBy call doesn't actually does any actual "grouping" - it just creates a lazy Grouping object. So effectively, you are going through the collection only once.

fold() into a set Koan is not accepted

I am learning Kotlin via Koans here: https://play.kotlinlang.org/koans/Collections/Fold/Task.kt
this is my code so far:
// Return the set of products that were ordered by all customers
fun Shop.getProductsOrderedByAll(): Set<Product> {
val orderedProductsByAll = this.customers.fold(mutableSetOf<Product>()) {
collector, c -> collector.plus(c.getOrderedProducts()).toMutableSet()
}
return orderedProductsByAll
}
fun Customer.getOrderedProducts(): List<Product> =
this.orders.flatMap { o -> o.products }
Unfortunately the results is not accepted:
Fail: testGetProductsOrderedByAllCustomers: The function 'getProductsOrderedByAll' is implemented incorrectly
Any tips on what I am doing wrong here?
You have probably misunderstood the question...
Return the set of products that were ordered by all customers
means
Give me the products that are been purchased by all customer
therefore, you don't want to return "all the products that are been purchased at least 1 time by any customer", but only those that every customer have bought at least one time
At the end, "mathematically speaking" you don't want an "union", but an "intersection"... and the collector has infact an intersect method...
OT note:
also, consider that your plus method is not doing anything to the set, and your code is equivalent to:
return customers.flatMap { c -> c.getOrderedProducts() }.toSet()

Optaplanner contraint streams - how to check mutliple resources

My domain is:
Requirement with some Variants of its realization
Any Variant need multiple resources with amount
Any Resource has limit
I try to write conflict method
constraintFactory
.from(Requirement::class.java)
.groupBy(Requirement::variant::resourceUsageList, sum(Resource::amount))
...
but is doesn't work
How can I get all used resource with its used amount and compare it with resources limit?
I think I need something like flatmap after from.
First make sure that your List<Resource> on your #PlanningSolution has a #ProblemFactCollectionProperty, so from(Resource.class) works.
Then I see multiple ways of doing it:
Proposal A)
from(Requirement.class)
.join(Resource.class) // Bi<Requirement, Resource>
.groupBy((requirement, resource) -> resource, sum((requirement, resource) -> requirement.variant.resourceUsage[resource.index]))
...
The downside is that this proposal A creates a Cartesian Product, so it can be costly memory wise if you have 100 resources and 10 000 requirements.
Propsal B)
.from(Requirement::class.java)
.groupBy(Requirement::variant::resourceUsageList, new MyResourceUsageSumCollector(...))
...
For MyResourceUsageSumCollector, which sums for each resource, look at this sum which sums for just one long for inspiration:
public static <A> UniConstraintCollector<A, ?, Long> sumLong(ToLongFunction<? super A> groupValueMapping) {
return new DefaultUniConstraintCollector<>(
() -> new long[1],
(resultContainer, a) -> {
long value = groupValueMapping.applyAsLong(a);
resultContainer[0] += value;
return () -> resultContainer[0] -= value;
},
resultContainer -> resultContainer[0]);
}

How to filter a list by using the ids of another list?

I have a list of ids. I want to filter my list and only keep the values in that list that match the id.
fun filterHelper(ids: List<Int>, list: List<People>) {
list.filter { ids.contains(it.id) }
}
But this is very inefficient. It is essentially traversing the list O(n^2). Does Kotlin let me do better?
I asked a similar question about slicing maps recently. The answer is that there is no good built-in function, but you can work around by using a Set instead of a List for your ids, which gets you O(1) lookup time for the comparisons, so O(n) in total.
data class People(val id: Int)
fun main() {
val people = listOf(People(1), People(2), People(3), People(4))
val ids = setOf(2, 4)
val filtered = people.filter { it.id in ids }
println(filtered)
}
Output:
[People(id=2), People(id=4)]
It's worth mentioning that if you already have a list, you can easily convert to a set with:
list.toSet()

How to get elements in one Flux that does not appear in another Flux

I am new to Spring Reactive Project. There was a problem in use.
I have two Flux, One has more elements, such as
Flux<Integer> bigFlux = Flux.range(1, 10);
And another likes
Flux<Integer> smallFlux = Flux.just(3, 7);
How can I get the elements in bigFlux that not appears in smallFlux?
I don't know which operator to use.
I have tried:
Flux<Integer> flux = bigFlux.filterWhen(one -> smallFlux.hasElement(one).map(a->!a));
But this is not wise, I got smallFlux through complex operations, such as querying the database, flatMap operations. In this way, how many elements in bigFlux, how many times these operations will be repeated.
In fact, smallFlux is obtained in this way.
Flux<File> usedFile = repository.findAll()
.flatMap(one -> {
List<File> used = someMethods(one);
return Flux.fromIterable(used);
});
Are there other better solutions, thanks.
I think this will be a cleaner and faster solution
final Flux<Integer> bigListFlux = Flux.just(1, 2, 3);
final Flux<Integer> smallListFlux = Flux.just(3, 5, 6);
Mono.zip(bigListFlux.collectList(), smallListFlux.collectList(), (bigList, smallList) -> {
bigList.removeAll(smallList);
return bigList;
}).flatMapMany(Flux::fromIterable).map(element -> {
System.out.println("element = " + element);
return element;
}).subscribe(); // do not use subscribe/block in actual production code.
I've used the following variant of Mono.zip
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#zip-reactor.core.publisher.Mono-reactor.core.publisher.Mono-java.util.function.BiFunction-