Group and calculate values in list - kotlin

I have a list of classes (Kotlin)
class ChallengeRecord(
val sc: String,
val participant: String,
val hr: String,
val point: Int)
how can I group by participant and sum points by them?
I want to found kotlin analogue for java
Collectors.groupingBy() or Collectors.toMap()

.groupingBy { it.participant }
.fold(0){accumulator, element -> accumulator + element.point }

.groupBy { it.participant }
.mapValues { (_, challengeRecords) -> challengeRecords.sumOf { it.point } }

Related

How to filter a list of objects based on a dynamically created collection of predicates

I am working on a searching/filtering functionality where a user should be able to filter a list of events to fit a pattern which he will make in runtime.
I made a function filter which is looping over all the constraints the user has set, and then filtering the result.
My problem is that I am copying the list many times, and I was wondering if there is a way where I can do this kind of complex filtering in a more declarative (kotlin-) way without side effects.
fun filter(query: Filter, eventsIn: List<Event>): List<Event> {
var events = eventsIn
query.filters.forEach { filter ->
if (filter.key is EventFiltersListStuff){
events = when (filter.key as EventFiltersListStuff) {
PLACE -> events.filter { event -> (filter.value as List<*>).contains(event.location.place) }
AREA -> events.filter { event -> (filter.value as List<*>).contains(event.location.area) }
CATEGORY -> events.filter { event -> (filter.value as List<*>).any{it in event.category} }
GENRE -> events.filter { event -> (filter.value as List<*>).contains(event.genre) }
}
} else {
events = when (filter.key as EventFilters) {
TITLE -> events.filter { event -> event.title.contains(filter.value as String, true) }
PRICELT -> events.filter { event -> event.price <= filter.value as Int }
PRICEGT -> events.filter { event -> event.price >= filter.value as Int }
TIMELT -> events.filter { event -> event.time <= filter.value as Int }
TIMEGT -> events.filter { event -> event.time >= filter.value as Int }
}
}
}
return events
}
The model looks like this
data class Event(
val title: String,
val genre: String,
val image: String,
val link: String,
val category: List<String>,
val price: Int,
val text: String,
val tickets: String,
var time: Long,
val location: Location
)
I have two enums one is for inclusive filtering, where a user can filter a list based on multiple instances of the attribute in focus.
The other one is non inclusive, and will just remove all entities which is not matching the query.
enum class EventFiltersListStuff(val str: String, ) : FilterType {
PLACE("place"),
AREA("area"),
CATEGORY("category"),
GENRE("genre");
override fun str(): String = str
}
enum class EventFilters(val str: String, ) : FilterType {
PRICELT("priceLT"),
PRICEGT("priceGT"),
TIMELT("timestampLT"),
TIMEGT("timestampGT"),
TITLE("title");
override fun str(): String = str
}
The next code block is less relevant, but I'll include is for transparency, Because it is used in the function which is the core of my question.
interface FilterType {
fun str(): String
}
class Filter private constructor(val filters: Map<FilterType, Any>) {
class Builder {
private var filters: MutableMap<FilterType, Any> = mutableMapOf()
fun filters(key: FilterType, value: Any) = apply {
this.filters[key] = when (this.filters[key]) {
is List<*> -> (this.filters[key] as List<*>) + listOf(value)
is Comparable<*> -> listOf(this.filters[key], value)
else -> value
}
}
fun build(): Filter {
return Filter(filters)
}
}
}
I am also using the Filter to generate a filter in a GraphQL query, this is the reason I am having strings in the enums.
Simple and efficient solution would be just to filter each event with conjunction of all filters.
typealias SingleFilter = Map.Entry<FilterType, Any> // you may want to remodel it as a sealed class, more on that later
fun SingleFilter.isMatching(event: Event): Boolean = ...
fun List<Event>.applyFilters(filters: List<SingleFilter>) = filter { event -> filters.all { it.isMatching(event) }
Your model could be greatly improved by remodeling your filters as a sealed class, replacing instance checks and enums with polymorphism. This will have a huge benefit of being type-safe. You can create a hierarchy with two groups for your two filtering cases. You can define mapping between filter names and classes in different part of your code, decoupling serialization from filter logic.

Passing Lamda function to Generic function not working

I am playing with Kotlin and I am trying to convert a working Scala code to Kotlin. Everything seems to go pretty well but the compiler gives me this error and I dont know how to handle it.
Type mismatch: inferred type is Any but ExQuestion was expected for this line: return makeMap(questions, add2)
I am using a generic function because I need to access members of type A when building the map and the members would be visible through the lambda function provided.
Here's the code which you can copy into the Kotlin sandbox:
data class ExQuestion(val area: String, val rId: String, val text: String, val rIdAnswer: String, val line: Long)
fun main() {
fun <A> makeMap(list: List<A>, addValue: (A, MutableMap<String, A>) -> Unit): Map<String, A> {
val map = mutableMapOf<String, A>()
for( item in list) {
addValue(item, map)
}
return map
}
val add2: (ExQuestion, MutableMap<String, ExQuestion>) -> Unit =
{ question: ExQuestion, map: MutableMap<String, ExQuestion> ->
val key = question.rId
if (map[key] == null) {
map[key] = question
} else {
println("Id Frage mehrfach vorhanden - " + key)
}
}
val questions = listOf(ExQuestion("Area", "Q01", "text", "A01",1))
return makeMap(questions, add2)
}
Working code:
data class ExQuestion(val area: String, val rId: String, val text: String, val rIdAnswer: String, val line: Long)
fun main() {
fun <A> makeMap(list: List<A>, addValue: (A, MutableMap<String, A>) -> Unit): Map<String, A> {
val map = mutableMapOf<String, A>()
for( item in list) {
addValue(item, map)
}
return map
}
val add2: (ExQuestion, MutableMap<String, ExQuestion>) -> Unit =
{ question: ExQuestion, map: MutableMap<String, ExQuestion> ->
val key = question.rId
if (map[key] == null) {
map[key] = question
} else {
println("Id Frage mehrfach vorhanden - " + key)
}
}
val questions = listOf(ExQuestion("Area", "Q01", "text", "A01",1))
val map = makeMap(questions, add2)
println(map.values)
}
I'm not sure what your question is, but you can convert your list of questions to a map keyed on rId by doing:
val map = questions.map { it.rId to it }.toMap()
println(map)
Result:
{Q01=ExQuestion(area=Area, rId=Q01, text=text, rIdAnswer=A01, line=1)}
Update in response to comments.
You can achieve that without a mutable map by doing something like this:
val map = questions
.groupBy { it.rId }
.mapValues { (key, values) ->
if (values.size > 1) println("Id Frage mehrfach vorhanden - $key")
values.first()
}
However, I think your mutable map solution is fine and arguably clearer, so this is just for demonstration.

Concise conversion of List<List<Double>> to Array<DoubleArray>?

I wrote this in Kotlin:
fun fromLists(cells: List<List<Double>>): Matrix {
return Matrix(cells.stream()
.map { x -> x.toDoubleArray() }
.toArray { i: Int -> Array(i, { k: Int -> DoubleArray(k) }) } )
}
Is there any way to reduce repetition in this code?
(Matrix itself is uninteresting, it just wraps an Array<DoubleArray>)
val ex1: Array<DoubleArray> = cells.map { it.toDoubleArray() }.toTypedArray()
// this should be faster, it doesn't create extra List like the previous example
val ex2: Array<DoubleArray> = Array(cells.size) { i -> cells[i].toDoubleArray() }

Return Single instead of Observable from iterable

I am trying to write a fun which has input as a list of Strings say UserIds and return as Single<>
The method looks something like this
fun getUserSubjectsForPeriod(userIds: List<String>, startDate: String, endDate: String): Single<Pair<String, List<UserSubjects>>> {
return Observable.fromIterable(userIds)
.map {id->
userSubjectRepository.loadUserSubjects(id, startDate, endDate)
.map {
val userSubjects = userSubjectMapper.mapToDomain(it)
Pair(id,userSubjects)
}
}
}
This function is returning
Observable<Single<Pair<String, List<UserSubjects>>>> instead of Single<Pair<String, List<UserSubjects>>>.
How do I return the required output?
You can use toList() method of Observable.
I don't know the signature of userSubjectRepository.loadUserSubjects so I supposed that it should return Single
fun getUserSubjectsForPeriod(userIds: List<String>, startDate: String, endDate: String): Single<List<Pair<String, UserSubjects>>> {
return Observable.fromIterable(userIds)
.flatMapSingle { id ->
userSubjectRepository.loadUserSubjects(id, startDate, endDate)
.map {
val userSubjects = userSubjectMapper.mapToDomain(it)
Pair(id, userSubjects)
}
}.toList()
}

How can I change this to use "for loop" instead of `forEach`

I'm struggling to change it to use a for loop and still do the same thing.
The program is supposed to read a file with some flights and this specific part of the program needs to read the file using two different days that the user inputs then it needs to show how many passengers there are per flight and each day.
And how it's done now works but I'm trying to change it to use a for loop as I said before but doesn't work because I don't know how to do the same thing as map does but only in the fun interval.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
map.forEach {
val reservasNum = it.key.first
val reservasDay = it.key.second
val reservasCount = it.value.count()
if (reservasDay == day) {
println("$reservasNum has $reservasCount passengers on day $day")
}
}
}
println()
println("Press Enter")
readLine()
}
fun mapReservas(reservas: List<Reservas>): Map<Pair<String, Int>, List<Reservas>> {
val map = mutableMapOf<Pair<String, Int>, MutableList<Reservas>>()
for (reserva in reservas) {
val key = reserva.numFlight to reserva.day
val list = map[key] ?: mutableListOf()
list.add(reserva)
map[key] = list
}
return map
}
All your code can be replaced only with one function.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
reservas.groupBy { reserva -> reserva.day to reserva.numFlight }
.filter { (key, _) -> key.first in dayInferior..daySuperior }
.forEach { (key, reservas) ->
val (reservasNum, reservasDay) = key
val reservasCount = reservas.count()
println("$reservasNum has $reservasCount passengers on day $reservasDay")
}
println()
println("Press Enter")
readLine()
}
Explaining:
As I undestand, at first you trying to group all your Reservas by day and numFlight. It can be done via one function groupBy where you pass pair of day and numFlight.
Filter all Reservas by day. It can be done by checking if day belongs to range dayInferior..daySuperior (operator in).
Print all reservas by using forEach.
Other things
Destructing declarations
val reservasNum = it.key.first
val reservasDay = it.key.second
same as
val (reservasNum, reservasDa) = it.key
Omitting one unused parameter in lamda:
.filter { (key, _) -> ... }
If you iterate with a for loop over the Map each element is a Pair. If you write (pair, list) you destructure each Pair which itself consists of a Pair and a List.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
for((pair, list) in map) {
val reservasNum = pair.first
val reservasDay = pair.second
val reservasCount = list.count()
// ...
}
}
// ...
}
Maybe this makes it more clear:
for(outerPair in map){
val (innerPair, list) = outerPair
val reservasNum = innerPair.first
val reservasDay = innerPair.second
val reservasCount = list.count()
// ...
}
I left this function (mapReservas) untouched intentionally, because maybe you are using it somewhere else. But you can improve it right away by using Type aliases (since Kotlin 1.1).
typealias FlightNum = String
typealias Day = Int
fun mapReservas(reservas: List<Reservas>):
Map<Pair<FlightNum, Day>, List<Reservas>> {
// ...
}
As you can see the code becomes much more readable if you use the destructure syntax and Type aliases.