2

Below function creates a Map, gets the count of passengers where passengers are > minTrips. The code works completely fine. Please see below

fun List<Trip>.filter(minTrips : Int): Set<Passenger> {
    var passengerMap: HashMap<Passenger, Int> = HashMap()

    this.forEach { it: Trip ->
        it.passengers.forEach { it: Passenger ->
            var count: Int? = passengerMap.get(it)
            if (count == null) {
                count = 1
                passengerMap.put(it, count)
            } else {
                count += 1
                passengerMap.put(it, count)
            }
        }
    }

    val filteredMinTrips: Map<Passenger, Int> = passengerMap.filterValues { it >= minTrips }
    println (" Filter Results = ${filteredMinTrips}")
    return filteredMinTrips.keys
}

Even though this is written in Kotlin, it seems like the code was first written in Java and then converted over to Kotlin. If it was truly written in Kotlin I am sure this wouldnt have been so many lines of code. How can I reduce the lines of Code? What would be a more funtional approach to solve this? What function or functions can I use to extract the Passengers Set directly where Passengers are > minTrips? This is too much of a code and seems crazy. Any pointers would be helpful here.

zsmb13
  • 85,752
  • 11
  • 221
  • 226
Ahmed
  • 2,966
  • 7
  • 42
  • 69

2 Answers2

7

One way you could do this is to take advantage of Kotlin's flatmap and grouping calls. By creating a list of all passengers on all trips, you can group them, count them, and return the ones that have over a certain number.

Assuming you have data classes like this (essential details only):

data class Passenger(val id: Int)
data class Trip(val passengers: List<Passenger>)

I was able to write this:

fun List<Trip>.frequentPassengers(minTrips: Int): Set<Passenger> =
    this
       .flatMap { it.passengers }
       .groupingBy { it }
       .eachCount()
       .filterValues { it >= minTrips }
       .keys

This is nice because it is a single expression. Going through it, we look at each Trip and extract all of its Passengers. If we had just done map here, we would have List<List<Passenger>>, but we want a List<Passenger> so we flatmap to achieve that. Next, we groupBy the Passenger objects themselves, and call eachCount() on the returned object, giving us a Map<Passenger, Int>. Finally we filter the map down the Passengers we find interesting, and return the set of keys.

Note that I renamed your function, List already has a filter on it, and even though the signatures are different I found it confusing.

Todd
  • 30,472
  • 11
  • 81
  • 89
  • 1
    Ok so I didnt knew what flatmap does. Seems really convenient :) – Ahmed Jan 12 '19 at 19:09
  • @Todd : Is there a way to extract two different types of objects (say driver and passenger) from a flat map at a same time? – Ahmed Jan 12 '19 at 21:53
  • @Ahmed yes, you could do this flatMap { it.passenger + it.drivers }.. what the code after that will look like depends on your use case – Willi Mentzel Jan 13 '19 at 10:58
1

You basically want to count the trips for each passenger, so you can put all passengers in a list and then group by them and afterwards count the occurences in each group:

fun List<Trip>.usualPassengers(minTrips : Int) = // 1
        flatMap(Trip::passengers) // 2
        .groupingBy { it } // 3
        .eachCount() // 4
        .filterValues { it >= minTrips } // 5
        .keys // 6

Explanation:

  1. return type Set<Passenger> can be inferred
  2. this can be ommitted, a list of the form [p1, p2, p1, p5, ...] is returned
  3. a Grouping is created, which looks like this [p1=[p1, p1], p2=[p2], ...]]
  4. the number of occurences in each group will be counted: [p1=2, p2=1, ...]
  5. all elementes with values which less than minTrips will be filtered out
  6. all keys that are left will be returned [p1, p2, ...]

p1...pn are Passenger instances

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121