Derek Peirce
05/03/2023, 12:34 AMequals
, hashCode
, and toString
for collections that each take a lambda, so you can specify a specific field that you want to check for equality or a substitute method to determine if two elements are equal, and so on. I don't think there's a simple way to write the equals
currently without involving sequences (preventing inlining), ensuring same size and then all
on indices and manually comparing (which is how I expect a method to be implemented, edit: that requires efficient random access, using the iterators directly would be required), or mapping each list to the desired field to compare or hash or whatnot (which means creating two unnecessary intermediate collections).ephemient
05/03/2023, 1:19 AMephemient
05/03/2023, 1:23 AMsetOf(1 to 3, 1 to 4, 2 to 5).equalsBy(setOf(1 to 3, 2 to 4, 2 to 5)) { it.first }
do you want it to be set equality (requiring mapping all the elements to reduce duplicates) or list equality (relying on iteration order which isn't determinate for all sets) or something else?Derek Peirce
05/03/2023, 1:27 AMList
and Array
should be feasible enoughephemient
05/03/2023, 1:39 AMhashMap
method, but .map(f).toString()
could be .joinToString(prefix = "[", postfix = "]") { f(it).toString() }
without the intermediate listDerek Peirce
05/03/2023, 1:40 AMhashCode
ephemient
05/03/2023, 1:42 AM.map(f).hashCode()
== .fold(1) { acc, x -> 31 * acc + f(x).hashCode() }
ephemient
05/03/2023, 1:43 AMList<A>
and a List<B>
which I want to use different mappers on before comparingDerek Peirce
05/03/2023, 1:47 AMlistOfA.equals(listOfB) { a, b -> customEquals(a, b) }
CLOVIS
05/03/2023, 7:55 AMzip
+`all`Derek Peirce
05/04/2023, 3:09 AMzip
creates an intermediate list of pairs, which shouldn't be necessary. If the two lists are usually not equal, so the method fails fast, we've made it Ω(N)
unnecessarily and created N+1 objects. We could zip
with `Sequence`s instead, but now we've instead created two Sequence
objects, and our lambda is no longer inlined. Both cases still require that we first check for size equality. The optimal method would look something like this:
inline fun <A, B> List<A>.equals(list: List<B>, equals: (A, B) -> Boolean): Boolean {
if (this === list) return true
if (size != list.size) return false
val iterA = iterator()
val iterB = list.iterator()
repeat(size) {
if (!equals(iterA.next(), iterB.next())) {
return false
}
}
return true
}
which is unwieldy enough that you definitely want it to be a dedicated method, it's just a question of whether or not it warrants a place in stdlib.ephemient
05/04/2023, 4:16 AMval iteraA = this.iterator()
val iterB = that.iterator()
while (iterA.hasNext() && iterB.hasNext()) if (!equals(iterA.next(), iterB.next()) return false
return iterA.hasNext() == iterB.hasNext()
is safe under thatDerek Peirce
05/04/2023, 4:20 AMList
methods are specified as thread-safe, including the existing equals
. You definitely want to do a size check before comparing the two lists. It would be a waste to iterate over both lists and check every single pair for equality only to realize at the very end that A had 701 elements and B had at least 702.ephemient
05/04/2023, 4:22 AMList
. CopyOnWriteArrayList#equals
is thread safeephemient
05/04/2023, 4:26 AMList.equals
is commonly implemented: https://github.com/openjdk/jdk17/blob/master/src/java.base/share/classes/java/util/AbstractList.java#L537, https://github.com/openjdk/jdk17/blob/master/src/java.base/share/classes/java/util/ArrayList.java#L516 (the latter only uses size as a hint, the former doesn't at all)Derek Peirce
05/04/2023, 4:26 AMCopyOnWriteArrayList
or Collections::synchronizedList
are specified to be thread-safe, but Kotlin's other collection extension methods are not written to be thread-safe.ephemient
05/04/2023, 4:27 AMDerek Peirce
05/04/2023, 4:28 AMConcurrentModificationException
would be an improvement, yes, but I think the size check should definitely come first.Derek Peirce
05/04/2023, 4:31 AMequalsArrayList
checks for size equality before doing any element checking. I don't know why equalsRange
wouldn't, though, that seems very easily wasteful.Derek Peirce
05/04/2023, 4:32 AMString
also checks for size equality before any elements.ephemient
05/04/2023, 4:34 AMDerek Peirce
05/04/2023, 4:36 AMequals
check, as if they are, the method will fail no longer how long it takes.Derek Peirce
05/04/2023, 4:43 AMnext()
should be sufficient. Looking at ArrayList
, next()
calls checkForComodification()
first, so NoSuchElementException
will not be called unless the list is modified concurrently with the next()
check itself, which any equals
method will risk.Derek Peirce
05/04/2023, 4:44 AMhasNext()
doesn't even have any comodification checks, so skipping it should be fine on any list that conforms to the rules of List
.ephemient
05/04/2023, 4:46 AMsize()
is called and when iterator()
is calledDerek Peirce
05/04/2023, 4:48 AMDerek Peirce
05/04/2023, 4:49 AMephemient
05/04/2023, 4:54 AMephemient
05/04/2023, 4:54 AMDerek Peirce
05/04/2023, 4:56 AMequals
method, that can be tons of unnecessary work just to conclude that two lists had different sizes, though.ephemient
05/04/2023, 4:58 AMephemient
05/04/2023, 4:59 AM