diff --git a/src/main/scala/scala/collection/decorators/MapDecorator.scala b/src/main/scala/scala/collection/decorators/MapDecorator.scala index bb7a90e..e3c7aa3 100644 --- a/src/main/scala/scala/collection/decorators/MapDecorator.scala +++ b/src/main/scala/scala/collection/decorators/MapDecorator.scala @@ -59,16 +59,16 @@ class MapDecorator[C, M <: IsMap[C]](coll: C)(implicit val map: M) { */ def mergeByKeyWith[W, X, That](other: Map[map.K, W])(f: PartialFunction[(Option[map.V], Option[W]), X])(implicit bf: BuildFrom[C, (map.K, X), That]): That = { val b = bf.newBuilder(coll) - val traversed = mutable.Set.empty[W] + val traversed = mutable.Set.empty[map.K] val pf = f.lift for { (k, v) <- map(coll) - x <- pf(other.get(k).fold[(Option[map.V], Option[W])]((Some(v), None)){ w => traversed += w; (Some(v), Some(w)) }) + x <- pf(other.get(k).fold[(Option[map.V], Option[W])]((Some(v), None)){ w => traversed += k; (Some(v), Some(w)) }) } { b += k -> x } for { - (k, w) <- other if !traversed(w) + (k, w) <- other if !traversed(k) x <- pf((None, Some(w))) } { b += k -> x diff --git a/src/test/scala/scala/collection/decorators/MapDecoratorTest.scala b/src/test/scala/scala/collection/decorators/MapDecoratorTest.scala index 8d8e474..3aae2fa 100644 --- a/src/test/scala/scala/collection/decorators/MapDecoratorTest.scala +++ b/src/test/scala/scala/collection/decorators/MapDecoratorTest.scala @@ -74,4 +74,92 @@ class MapDecoratorTest { // Assert.assertEquals(expected, zipped2) } + @Test + def mergingByKeyPerformsFullOuterJoin(): Unit = { + val arthur = "arthur.txt" + + val tyson = "tyson.txt" + + val sandra = "sandra.txt" + + val allKeys = Set(arthur, tyson, sandra) + + val sharedValue = 1 + + val ourChanges = Map( + ( + arthur, + sharedValue + ), + ( + tyson, + 2 + ) + ) + + locally { + // In this test case, none of the associated values collide across keys... + + val theirChanges = Map( + ( + arthur, + sharedValue + ), + ( + sandra, + 3 + ) + ) + + Assert.assertEquals("Expect the same keys to appear in the join taken either way around.", ourChanges.mergeByKey(theirChanges).keySet, theirChanges + .mergeByKey(ourChanges) + .keySet) + + Assert.assertTrue("Expect the same associated values to appear in the join taken either way around, albeit swapped around and not necessarily in the same key order.", + ourChanges + .mergeByKey(theirChanges) + .values + .map(_.swap) + .toList + .sorted + .sameElements(theirChanges.mergeByKey(ourChanges).values.toList.sorted)) + + Assert.assertEquals("Expect all the keys to appear in an outer join.", ourChanges.mergeByKey(theirChanges).keySet, allKeys) + + Assert.assertEquals("Expect all the keys to appear in an outer join.", theirChanges.mergeByKey(ourChanges).keySet, allKeys) + } + + locally { + // In this test case, associated values collide across keys... + + val theirChangesRedux = Map( + ( + arthur, + sharedValue + ), + ( + sandra, + sharedValue + ) + ) + + Assert.assertEquals("Expect the same keys to appear in the join taken either way around.", ourChanges.mergeByKey(theirChangesRedux).keySet, theirChangesRedux + .mergeByKey(ourChanges) + .keySet) + + Assert.assertTrue("Expect the same associated values to appear in the join taken either way around, albeit swapped around and not necessarily in the same key order.", + ourChanges + .mergeByKey(theirChangesRedux) + .values + .map(_.swap) + .toList + .sorted + .sameElements(theirChangesRedux.mergeByKey(ourChanges).values.toList.sorted)) + + Assert.assertEquals("Expect all the keys to appear in an outer join.", ourChanges.mergeByKey(theirChangesRedux).keySet, allKeys) + + Assert.assertEquals("Expect all the keys to appear in an outer join.", theirChangesRedux.mergeByKey(ourChanges).keySet, allKeys) + } + } + }