Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin distinctBy with condition

Tags:

kotlin

I have array with multiple objects with the same key, Other objects have empty values and I was hoping to use distinctBy to remove duplicates and get objects which values has the longest string.

data class ObjA(val key: String, val value: String)

fun test() {
    val listOfA = mutableListOf(
            ObjA("one", ""), //This will be taken in distinctBy.
            ObjA("one", "o"),
            ObjA("one", "on"),
            ObjA("one", "one"), //This will be ignored in distinctBy. I WANT THIS ONE.

            ObjA("two", ""), //This will be returned in distinctBy
            ObjA("two", "2"),
            ObjA("two", "two"), //But I want this.

            ObjA("three", "3"),
            ObjA("four", "4"),
            ObjA("five", "five")
    )

    val listOfAWithNoDuplicates = listOfA.distinctBy { it.key }

    for (o in listOfAWithNoDuplicates) {
        println("key: ${o.key}, value: ${o.value}")
    }
}

Output

key: one, value: 
key: two, value: 
key: three, value: 3
key: four, value: 4
key: five, value: five

How to make this work. Any help will be appreciated.

like image 200
Nux Avatar asked May 22 '19 10:05

Nux


People also ask

How do you use Distinctby in Kotlin?

Returns a list containing only elements from the given array having distinct keys returned by the given selector function. Among elements of the given array with equal keys, only the first one will be present in the resulting list.


1 Answers

As distinctBy just returns the distinct keys based on your selector (and in the order of the list), you end up with unique keys, but not yet with the values you want.

For that particular use-case I would probably just sort beforehand, followed by the distinctBy

listOfA.sortedByDescending { it.value.length }
       .distinctBy { it.key }

Which creates a new list at sortedByDescending or you just sort the current list beforehand (sortByDescending) and apply distinctBy later on, e.g.:

listOfA.sortByDescending { it.value.length }
listOfA.distinctBy { it.key }

In both cases you get a new List<ObjA> with the expected values.

Several other variants come to my mind as well. All those variants will put the results into a Map<String, ObjA> where the key is actually the unique ObjA.key. You may want to call .values directly if you are not interested in the key/ObjA-mapping.

  1. variant using groupingBy and reduce:

    listOfA.groupingBy { it.key }
           .reduce { _, acc, e->
             maxOf(e, acc, compareBy { it.value.length })
           }
    
  2. variant using a plain forEach/for and filling its own Map:

    val map = mutableMapOf<String, ObjA>()
    listOfA.forEach {
        map.merge(it.key, it) { t, u ->
            maxOf(t, u, compareBy { it.value.length })
        }
    }
    
  3. variant using fold and merge (very similar to the previous... just using fold instead of for/forEach):

    listOfA.fold(mutableMapOf<String, ObjA>()) { acc, objA ->
      acc.apply {
        merge(objA.key, objA) { t, u ->
          maxOf(t, u, compareBy { it.value.length })
        }
      }
    }
    
  4. variant using groupBy followed by mapValues (but you are then actually creating 1 map which you discard immediately):

    listOfA.groupBy { it.key } // the map created at this step is discarded and all the values are remapped in the next step
           .mapValues { (_, values) ->
              values.maxBy { it.value.length }!!
           }
    
like image 112
Roland Avatar answered Sep 28 '22 15:09

Roland