From 8b9c93bc10a988157e5c86eb4010af75e82f6a4d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 7 Mar 2023 19:12:12 +0600 Subject: [PATCH] diffs improvement --- CHANGELOG.md | 4 ++ .../dev/inmo/micro_utils/common/DiffUtils.kt | 63 ++++++++++++++++--- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22adde6cb02..19e95aed9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.17.3 +* `Common`: + * New function `emptyDiff` + * Now you may pass custom `comparisonFun` to all `diff` functions + ## 0.17.2 * `FSM`: diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt index 367c267ac77..ef27b7f5b5b 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt @@ -36,6 +36,8 @@ data class Diff internal constructor( val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue> ) +fun emptyDiff(): Diff = Diff(emptyList(), emptyList(), emptyList()) + private inline fun performChanges( potentialChanges: MutableList?, IndexedValue?>>, additionsInOld: MutableList, @@ -43,14 +45,14 @@ private inline fun performChanges( changedList: MutableList, IndexedValue>>, removedList: MutableList>, addedList: MutableList>, - strictComparison: Boolean + comparisonFun: (T?, T?) -> Boolean ) { var i = -1 val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { i++ - val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison) - val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison) + val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value) + val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value) if (oldOneEqualToNewObject || newOneEqualToOldObject) { changedList.addAll( potentialChanges.take(i).mapNotNull { @@ -104,7 +106,7 @@ private inline fun performChanges( */ fun Iterable.calculateDiff( other: Iterable, - strictComparison: Boolean = false + comparisonFun: (T?, T?) -> Boolean ): Diff { var i = -1 var j = -1 @@ -132,7 +134,7 @@ fun Iterable.calculateDiff( } when { - oldObject === newObject || (oldObject == newObject && !strictComparison) -> { + comparisonFun(oldObject, newObject) -> { changedObjects.addAll(potentiallyChangedObjects.map { @Suppress("UNCHECKED_CAST") it as Pair, IndexedValue> @@ -143,23 +145,49 @@ fun Iterable.calculateDiff( potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) val previousOldsAdditionsSize = additionalInOld.size val previousNewsAdditionsSize = additionalInNew.size - performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) + performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) i -= (additionalInOld.size - previousOldsAdditionsSize) j -= (additionalInNew.size - previousNewsAdditionsSize) } } } potentiallyChangedObjects.add(null to null) - performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) + performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) } + +/** + * Calculating [Diff] object + * + * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different + * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links + */ +fun Iterable.calculateDiff( + other: Iterable, + strictComparison: Boolean = false +): Diff = calculateDiff( + other, + comparisonFun = if (strictComparison) { + { t1, t2 -> + t1 === t2 + } + } else { + { t1, t2 -> + t1 == t2 + } + } +) inline fun Iterable.diff( other: Iterable, strictComparison: Boolean = false ): Diff = calculateDiff(other, strictComparison) +inline fun Iterable.diff( + other: Iterable, + noinline comparisonFun: (T?, T?) -> Boolean +): Diff = calculateDiff(other, comparisonFun) -inline fun Diff(old: Iterable, new: Iterable) = old.calculateDiff(new) +inline fun Diff(old: Iterable, new: Iterable) = old.calculateDiff(new, strictComparison = false) inline fun StrictDiff(old: Iterable, new: Iterable) = old.calculateDiff(new, true) /** @@ -187,3 +215,22 @@ fun MutableList.applyDiff( set(new.index, new.value) } } + +/** + * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] + * mutable list + */ +fun MutableList.applyDiff( + source: Iterable, + comparisonFun: (T?, T?) -> Boolean +): Diff = calculateDiff(source, comparisonFun).also { + for (i in it.removed.indices.sortedDescending()) { + removeAt(it.removed[i].index) + } + it.added.forEach { (i, t) -> + add(i, t) + } + it.replaced.forEach { (_, new) -> + set(new.index, new.value) + } +}