I’ve got a `data class Thing(val x: Int, val y: In...
# codereview
j
I’ve got a
data 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.
Copy code
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)
j
Depending on what you need to do, sometimes
distinctBy { ... }
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 fields
j
Close! subtractBy would do the job
j
Cool, didn't know about
subtractBy
🙂
j
it doesn’t exist 😞
I think for
selector
on this page is the place I think I’d find it. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/
I think using a normal (i.e. not-necessarily-ordered) set and wrapping, then unwrapping afterward, is the way forward here. Thanks for your help!
j
You could implement your own
subtractBy
based on this idea btw, maybe you can find inspiration in the implementation of
distinctBy
j
distinct could be O(n). But for two arguments, I think I’d either end up with O(n^2) or end up re-inventing hashing and equality.
Although I now realise that if I wrap in a special purpose data class I still have the same problem of the wrapping field being included in the equality. So I can’t escape implementing equals.
j
mmmh not sure I get your point. You could literally implement it the same way as
distinctBy
by just initializing the hashset with the elements of the second set, couldn't you?
I'm talking about this implementation of `distinctBy`:
Copy code
public 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
}
I didn't give it much thought, but if you call it
subtractBy
, 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)
j
Oh maybe it’s easier than I thought.
Copy code
public 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
}
or whatever.
j
I think that's more complicated than it needs to be. Why not just:
Copy code
inline 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
}
j
Except no, that’s the problem. You have to remove the original object which mapped to that
selector
result. So you need to store a hash table of selected value to original.
Yeah I think that’s what I meant.
Afraid I have to run out now, thanks very much for your ideas.
j
You don't have to remove nor store anything, you just insert original elements in final list only if there is no match on their key in the "visited" set
j
Thanks, you’re right. I was thinking about it backwards.