amazon-ion / ion-element-kotlin

IonElement is an immutable in-memory representation of the Ion data format. IonElement's API is idiomatic to Kotlin.
Apache License 2.0
8 stars 8 forks source link

For container types, check hash code before deep equality #58

Open dlurton opened 3 years ago

dlurton commented 3 years ago

For example, the StructElementImpl class implements Any.equals using the code shown below. Checking if two containers are equal could be considerably faster if the Any.equals override for each container type considered the Any.hashCode before checking deep equality. The hash code is already lazily computed and never re-computed, so this has the potential to significantly increase performance for certain uses of this library.

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is StructElementImpl) return false

        // TODO: compare hash code here--if not equal, values are not equal

        if (annotations != other.annotations) return false

        // We might avoid materializing fieldsByName by checking fields.size first
        if (this.size != other.size) return false
        if (this.fieldsByName.size != other.fieldsByName.size) return false

        // If we make it this far we can compare the list of field names in both
        if(this.fieldsByName.keys != other.fieldsByName.keys) return false

        // If we make it this far then we have to take the expensive approach of comparing the individual values in
        // [this] and [other].
        //
        // A field group is a list of fields with the same name. Within each field group we count the number of times
        // each value appears in both [this] and [other]. Each field group is equivalent if every value that appears n
        // times in one group also appears n times in the other group.

        this.fieldsByName.forEach { thisFieldGroup ->
            val thisSubGroup: Map<AnyElement, Int> = thisFieldGroup.value.groupingBy { it }.eachCount()

            // [otherGroup] should never be null due to the `if` statement above.
            val otherGroup = other.fieldsByName[thisFieldGroup.key]
                             ?: error("unexpectedly missing other field named '${thisFieldGroup.key}'")

            val otherSubGroup: Map<AnyElement, Int> = otherGroup.groupingBy { it }.eachCount()

            // Simple equality should work from here
            if(thisSubGroup != otherSubGroup) {
                return false
            }
        }

        // Metas intentionally not included here.

        return true
    }