Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit f2b83e2

Browse files
committed
compare correctly lists with duplicates using objectMatcher
1 parent 73f59ff commit f2b83e2

File tree

3 files changed

+49
-17
lines changed

3 files changed

+49
-17
lines changed

core/src/main/scala/com/softwaremill/diffx/Matching.scala

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@ private[diffx] object Matching {
1616
MatchingResults(left.diff(matchedKeys.map(_._1)), right.diff(matchedKeys.map(_._2)), matchedKeys)
1717
}
1818

19-
private[diffx] def matching[T](
20-
left: scala.collection.Set[T],
21-
right: scala.collection.Set[T],
22-
matcher: ObjectMatcher[T]
23-
): MatchingResults[T] = {
24-
val matchedKeys = left.flatMap(l =>
25-
right.collectFirst {
26-
case r if matcher.isSameObject(l, r) => l -> r
27-
}
28-
)
29-
MatchingResults(left.diff(matchedKeys.map(_._1)), right.diff(matchedKeys.map(_._2)), matchedKeys)
30-
}
31-
3219
private[diffx] case class MatchingResults[T](
3320
unmatchedLeft: scala.collection.Set[T],
3421
unmatchedRight: scala.collection.Set[T],

core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.softwaremill.diffx.instances
22

3-
import com.softwaremill.diffx.Matching.{MatchingResults, matching}
3+
import com.softwaremill.diffx.Matching.MatchingResults
44
import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry}
55
import com.softwaremill.diffx._
66

7+
import scala.annotation.tailrec
78
import scala.collection.immutable.{ListMap, ListSet}
89

910
private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](
@@ -16,12 +17,12 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](
1617

1718
val leftAsMap = left.toList.lift
1819
val rightAsMap = right.toList.lift
19-
val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) }
20-
val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) }
20+
val lEntries = keys.map(i => i -> leftAsMap(i)).collect { case (k, Some(v)) => MapEntry(k, v) }.toList
21+
val rEntries = keys.map(i => i -> rightAsMap(i)).collect { case (k, Some(v)) => MapEntry(k, v) }.toList
2122

2223
val adjustedMatcher = context.getMatcherOverride[IterableEntry[T]].getOrElse(matcher)
2324
val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) =
24-
matching(leftv2, rightv2, adjustedMatcher)
25+
matchPairs(lEntries, rEntries, adjustedMatcher)
2526
val leftDiffs = unMatchedLeftInstances
2627
.diff(unMatchedRightInstances)
2728
.collectFirst { case MapEntry(k, v) => k -> DiffResultAdditional(v) }
@@ -35,4 +36,32 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](
3536
val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*)
3637
DiffResultObject("List", diffs)
3738
}
39+
40+
private def matchPairs(
41+
left: List[IterableEntry[T]],
42+
right: List[IterableEntry[T]],
43+
matcher: ObjectMatcher[IterableEntry[T]]
44+
): MatchingResults[IterableEntry[T]] = {
45+
@tailrec
46+
def loop(
47+
left: List[IterableEntry[T]],
48+
right: List[IterableEntry[T]],
49+
matches: List[(IterableEntry[T], IterableEntry[T])],
50+
leftUnmatched: List[IterableEntry[T]]
51+
): MatchingResults[IterableEntry[T]] = {
52+
left match {
53+
case lHead :: tail =>
54+
val maybeMatch = right.collectFirst {
55+
case r if matcher.isSameObject(lHead, r) => lHead -> r
56+
}
57+
maybeMatch match {
58+
case Some(m @ (_, rm)) =>
59+
loop(tail, right.filterNot(r => r.key == rm.key), matches :+ m, leftUnmatched)
60+
case None => loop(tail, right, matches, leftUnmatched :+ lHead)
61+
}
62+
case Nil => MatchingResults(ListSet(leftUnmatched: _*), ListSet(right: _*), ListSet(matches: _*))
63+
}
64+
}
65+
loop(left, right, List.empty, List.empty)
66+
}
3867
}

core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,22 @@ class DiffTest extends AnyFreeSpec with Matchers {
395395
compare(o1, o2).isIdentical shouldBe true
396396
}
397397

398+
"compare lists using value object matcher" in {
399+
val p2WithSameNameAsP1 = p2.copy(name = p1.name)
400+
val o1 = Organization(List(p1, p2WithSameNameAsP1))
401+
val o2 = Organization(List(p2WithSameNameAsP1, p1))
402+
implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(identity(_))
403+
compare(o1, o2).isIdentical shouldBe true
404+
}
405+
406+
"compare correctly lists with duplicates using objectMatcher" in {
407+
val o1 = Organization(List(p1, p1))
408+
val o2 = Organization(List(p1, p1))
409+
implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(identity(_))
410+
val result = compare(o1, o2)
411+
result.isIdentical shouldBe true
412+
}
413+
398414
"should preserve order of elements" in {
399415
val l1 = List(1, 2, 3, 4, 5, 6)
400416
val l2 = List(1, 2, 3, 4, 5, 7)

0 commit comments

Comments
 (0)