jwass
02/03/2022, 2:03 PMdata class Thing(val x: Int, val y: Int, val ignoreMe: String) which I want to put in a set to perform set operations, but ignore certain fields for the purpose of set comparison. i.e. only considering a subset of fields for equality comparisons.
val x = setOf(Thing(5, 5, "Hello"), Thing(10, 6, "Goodbye"), Thing(100, 3, "Zzzz"))
val y = setOf(Thing(5, 5, "Baz"), Thing(10, 6, "Goodbye"))
val result = x.minus(y)
-> result = setOf(Thing(100, 3, "Zzzz")) // because Thing(5, 5, "Hello") is considered equal to Thing(5, 5, "Baz")
In reality the data classes are a bit richer, about 6 fields.
I thought I could build a sorted set with a custom comparator, but that requires implementing a sorting on the data classes when I only really want a custom equality for that set.
I only want this custom equality behaviour in one context, so it’s not appropriate to actually re-implement equals etc on the data classes directly.
Any ideas about if there’s a simple way to achieve this?
(edit - found a nice idea on stack overflow - just wrap the objects in a special purpose class)Joffrey
02/03/2022, 2:07 PMdistinctBy { ... } may be sufficient. Otherwise you can also use a regular HashSet but convert your items into some other data class containing a subset of the fieldsjwass
02/03/2022, 2:09 PMJoffrey
02/03/2022, 2:10 PMsubtractBy 🙂jwass
02/03/2022, 2:10 PMjwass
02/03/2022, 2:10 PMselector on this page is the place I think I’d find it. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/jwass
02/03/2022, 2:11 PMJoffrey
02/03/2022, 2:12 PMsubtractBy based on this idea btw, maybe you can find inspiration in the implementation of distinctByjwass
02/03/2022, 2:15 PMjwass
02/03/2022, 2:16 PMJoffrey
02/03/2022, 2:17 PMdistinctBy by just initializing the hashset with the elements of the second set, couldn't you?Joffrey
02/03/2022, 2:18 PMpublic inline fun <T, K> Iterable<T>.distinctBy(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (e in this) {
val key = selector(e)
if (set.add(key))
list.add(e)
}
return list
}Joffrey
02/03/2022, 2:19 PMsubtractBy, pass another set as first argument, and initialize the first hashset with elements of the set passed as argument, it seems it would do the trick.
(and you'll probably want to replace ArrayList with a HashSet too if you want to keep it a set)jwass
02/03/2022, 2:22 PMpublic inline fun <T, K> Iterable<T>.subtractBy(selector: (T) -> K, other: Iterable<T>): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (e in this) {
val key = selector(e)
if (set.add(key))
list.add(e)
}
for (e in other){
val key = selector(e)
if (set.contains(key)) {
set.remove(e)
}
}
return list
}jwass
02/03/2022, 2:22 PMJoffrey
02/03/2022, 2:24 PMinline fun <T, K> Iterable<T>.subtractBy(other: Set<T>, selector: (T) -> K): List<T> {
val visited = other.mapTo(HashSet(), selector)
val result = ArrayList<T>()
for (e in this) {
val key = selector(e)
if (visited.add(key))
result.add(e)
}
return result
}jwass
02/03/2022, 2:24 PMselector result. So you need to store a hash table of selected value to original.jwass
02/03/2022, 2:25 PMjwass
02/03/2022, 2:25 PMJoffrey
02/03/2022, 2:25 PMjwass
02/03/2022, 6:52 PM