3

Let's assume i have a class with two instances:

class Car(val id: Int, val color: Color, val pistons: Int, val spoiler: Boolean) val audi = Car(1234, Color.BLUE, 8, false) val bmw = Car(4321, Color.WHITE, 6, false) 

Now i'd like to check equality for some properties (not all -> i'd use a data class in that case)

fun looksSimilar(a: Car, b: Car) = a.color == b.color && a.spoiler == b.spoiler 

I'm now looking for a method which does comparison:

  • for more generic objects T and their properties
  • more idiomatic: no-one wants to read tons of equals-checks
  • similarly fast

I've come up with the following proposal:

fun <T> Pair<T, T>.equalIn(vararg arguments: (T) -> Any?) = arguments.toList().all { it(first) == it(second) } 

which allows me to write the above check as

val looksSimilar = (audi to bmw).equalIn({it.color}, {it.spoiler}) 

Does anyone know a better (e.g. cleaner/faster) solution?


My usecase is the following:

I'm writing an Android App with multiple RecyclerView's (= fancy view to display lists)

Each RecyclerView has a ListAdapter (responsible for the underlying list)

Each ListAdapter requires a DiffUtil.ItemCallback (for comparing old & new Items & initiating appropiate changes in the view)

val callback = object : DiffUtil.ItemCallback<Car>() { override fun areItemsTheSame(oldItem: Car, newItem: Car): Boolean // usually checks for id, e.g. oldItem.id == newItem.id override fun areContentsTheSame(oldItem: Car, newItem: Car): Boolean // checks if two items look the same. // Used for fancy enter/exit animations afaik. // e.g. (oldItem to newItem).equalIn({it.color}, {it.spoiler}) } 
7
  • 1
    Tbh, I would heavily prefer simple stupid equality checks over anything more fancy because everyone knows how they look and work. It took my almost 2 minutes to fully understand what your function does and how it works. If there were only simple equality checks instead I would understand them in one second when reading the code. Commented Dec 11, 2020 at 15:13
  • 1
    At the usage site it would be cleaner to write .equalIn(Car::color, Car::spoiler). Otherwise, your way seems fine to me. Maybe it's a little awkward to require the intermediate Pair. Other than that, this is creating functional objects and arrays to do the check, so there's a bit of GC churn that you wouldn't have by using == comparisons directly. Commented Dec 11, 2020 at 15:16
  • 1
    You don't need any of this if instead of using just class you use data class that is exactly the reason why data class exist, it allows structural comparison by just saying a == b, your Pistons would also have to be a data class. And usually in the diff item callback the areItemsTheSame is the ids comparison and then on the content you use oldItem == newItem Commented Dec 11, 2020 at 15:20
  • @vatbub i agree that, in the moment, it's not perfect to read; but that's part of my question Commented Dec 11, 2020 at 15:45
  • 1
    Personally instead of the extension function I'd probably make some kind of comparison class (like a Comparator that only handles equals) so you can create an instance of it, configured how you like, and just use that for all your checks. You could pass in your property list during construction, to define all the checks it should do, and then you create it once - and you can have different variations if you need them Commented Dec 11, 2020 at 17:23

1 Answer 1

2

While browsing by StackOverflow history, I found this question again and it tickled my brain, so I thought about it a little. I still stand by my opinion that I prefer explicit if checks (easier to understand IMO, fastest performance), but if I had to do it with an extension function, I would rather use a list instead of a pair (allowing for more than two inputs) and I would use reference syntax for the lambdas at the call site, making it a little more concise:

fun <T> List<T>.equalIn(vararg arguments: (T) -> Any?): Boolean { if (isEmpty()) return false val argumentsList = arguments.toList() return all { item -> argumentsList.all { it(item) == it(first()) } } } fun main() { val audi = Car(1234, Color.BLUE, 8, false) val bmw = Car(4321, Color.BLUE, 6, false) println(listOf(audi, bmw).equalIn(Car::color, Car::spoiler)) } 
Sign up to request clarification or add additional context in comments.

2 Comments

Nice to see a year old question still tickle your brain :D
I would use if (isEmpty()) return true though.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.