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 AMhashCodeephemient
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