Skip to content

Commit 9ae2e0e

Browse files
committed
Use chain for more efficient concatenation
1 parent 731e692 commit 9ae2e0e

File tree

1 file changed

+35
-32
lines changed

1 file changed

+35
-32
lines changed

core/src/main/scala/diffson/jsonpatch/JsonDiff.scala

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,70 +23,72 @@ import jsonpointer._
2323
import cats.implicits._
2424

2525
import scala.annotation.tailrec
26+
import cats.data.Chain
2627

2728
class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony[Json], Lcs: Lcs[Json])
2829
extends Diff[Json, JsonPatch[Json]] {
2930
def diff(json1: Json, json2: Json): JsonPatch[Json] =
30-
JsonPatch(diff(json1, json2, Pointer.Root))
31+
JsonPatch(diff(json1, json2, Pointer.Root).toList)
3132

32-
private def diff(json1: Json, json2: Json, pointer: Pointer): List[Operation[Json]] =
33+
private def diff(json1: Json, json2: Json, pointer: Pointer): Chain[Operation[Json]] =
3334
if (json1 === json2)
3435
// if they are equal, this one is easy...
35-
Nil
36+
Chain.empty
3637
else
3738
(json1, json2) match {
3839
case (JsObject(fields1), JsObject(fields2)) => fieldsDiff(fields1.toList, fields2.toList, pointer)
3940
case (JsArray(arr1), JsArray(arr2)) if diffArray => arraysDiff(arr1.toList, arr2.toList, pointer)
40-
case (_, _) => List(Replace(pointer, json2, if (rememberOld) Some(json1) else None))
41+
case (_, _) => Chain.one(Replace(pointer, json2, if (rememberOld) Some(json1) else None))
4142
}
4243

4344
private def fieldsDiff(fields1: List[(String, Json)],
4445
fields2: List[(String, Json)],
45-
path: Pointer): List[Operation[Json]] = {
46+
path: Pointer): Chain[Operation[Json]] = {
4647
// sort fields by name in both objects
4748
val sorted1 = fields1.sortBy(_._1)
4849
val sorted2 = fields2.sortBy(_._1)
4950
@tailrec
5051
def associate(fields1: List[(String, Json)],
5152
fields2: List[(String, Json)],
52-
acc: List[(Option[(String, Json)], Option[(String, Json)])])
53-
: List[(Option[(String, Json)], Option[(String, Json)])] = (fields1, fields2) match {
53+
acc: Chain[(Option[(String, Json)], Option[(String, Json)])])
54+
: Chain[(Option[(String, Json)], Option[(String, Json)])] = (fields1, fields2) match {
5455
case (f1 :: t1, f2 :: t2) if f1._1 == f2._1 =>
5556
// same name, associate both
56-
associate(t1, t2, (Some(f1), Some(f2)) :: acc)
57+
associate(t1, t2, acc.prepend((Some(f1), Some(f2))))
5758
case (f1 :: t1, f2 :: _) if f1._1 < f2._1 =>
5859
// the first field is not present in the second object
59-
associate(t1, fields2, (Some(f1), None) :: acc)
60+
associate(t1, fields2, acc.prepend((Some(f1), None)))
6061
case (_ :: _, f2 :: t2) =>
6162
// the second field is not present in the first object
62-
associate(fields1, t2, (None, Some(f2)) :: acc)
63+
associate(fields1, t2, acc.prepend((None, Some(f2))))
6364
case (_, Nil) =>
64-
fields1.map(Some(_) -> None) ::: acc
65+
Chain.fromSeq(fields1.map(Some(_) -> None)) ++ acc
6566
case (Nil, _) =>
66-
fields2.map(None -> Some(_)) ::: acc
67+
Chain.fromSeq(fields2.map(None -> Some(_))) ++ acc
6768
}
69+
6870
@tailrec
6971
def fields(fs: List[(Option[(String, Json)], Option[(String, Json)])],
70-
acc: List[Operation[Json]]): List[Operation[Json]] = fs match {
72+
acc: Chain[Operation[Json]]): Chain[Operation[Json]] = fs match {
7173
case (Some(f1), Some(f2)) :: tl if f1 == f2 =>
7274
// all right, nothing changed
7375
fields(tl, acc)
7476
case (Some(f1), Some(f2)) :: tl =>
7577
// same field name, different values
76-
fields(tl, diff(f1._2, f2._2, path / f1._1) ::: acc)
78+
fields(tl, diff(f1._2, f2._2, path / f1._1) ++ acc)
7779
case (Some(f1), None) :: tl =>
7880
// the field was deleted
79-
fields(tl, Remove[Json](path / f1._1, if (rememberOld) Some(f1._2) else None) :: acc)
81+
fields(tl, acc.prepend(Remove[Json](path / f1._1, if (rememberOld) Some(f1._2) else None)))
8082
case (None, Some(f2)) :: tl =>
8183
// the field was added
82-
fields(tl, Add(path / f2._1, f2._2) :: acc)
84+
fields(tl, acc.prepend(Add(path / f2._1, f2._2)))
8385
case _ =>
8486
acc
8587
}
86-
fields(associate(sorted1, sorted2, Nil), Nil)
88+
fields(associate(sorted1, sorted2, Chain.empty).toList, Chain.empty)
8789
}
8890

89-
private def arraysDiff(arr1: List[Json], arr2: List[Json], path: Pointer): List[Operation[Json]] = {
91+
private def arraysDiff(arr1: List[Json], arr2: List[Json], path: Pointer): Chain[Operation[Json]] = {
9092
// get the longest common subsequence in the array
9193
val lcs = Lcs.lcs(arr1, arr2)
9294

@@ -104,15 +106,16 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
104106

105107
// add a bunch of values to an array starting at the specified index
106108
@tailrec
107-
def add(arr: List[Json], idx: Int, acc: List[Operation[Json]]): List[Operation[Json]] = arr match {
108-
case v :: tl => add(tl, idx + 1, Add(path / idx, v) :: acc)
109-
case Nil => acc.reverse
109+
def add(arr: List[Json], idx: Int, acc: Chain[Operation[Json]]): Chain[Operation[Json]] = arr match {
110+
case v :: tl => add(tl, idx + 1, acc.append(Add(path / idx, v)))
111+
case Nil => acc
110112
}
111113

112114
// remove a bunch of array elements starting by the last one in the range
113-
def remove(from: Int, until: Int, shift: Int, arr: List[Json]): List[Operation[Json]] =
114-
(for (idx <- until to from by -1)
115-
yield Remove[Json](path / idx, if (rememberOld) Some(arr(idx - shift)) else None)).toList
115+
def remove(from: Int, until: Int, shift: Int, arr: List[Json]): Chain[Operation[Json]] =
116+
Chain.fromSeq(
117+
for (idx <- until to from by -1)
118+
yield Remove[Json](path / idx, if (rememberOld) Some(arr(idx - shift)) else None))
116119

117120
// now iterate over the first array to computes what was added, what was removed and what was modified
118121
@tailrec
@@ -123,8 +126,8 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
123126
shift1: Int, // current index shift in the first array (due to elements being add or removed)
124127
idx2: Int, // current index in the second array
125128
lcs: List[(Int, Int)], // the list of remaining matching indices
126-
acc: List[Operation[Json]] // the already accumulated result
127-
): List[Operation[Json]] = (arr1, arr2) match {
129+
acc: Chain[Operation[Json]] // the already accumulated result
130+
): Chain[Operation[Json]] = (arr1, arr2) match {
128131
case (_ :: tl1, _) if isCommon1(idx1, lcs) =>
129132
// all values in arr2 were added until the index of common value
130133
val until = lcs.head._2
@@ -134,7 +137,7 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
134137
shift1 + until - idx2,
135138
until + 1,
136139
lcs.tail,
137-
add(arr2.take(until - idx2), idx1 + shift1, Nil) reverse_::: acc)
140+
acc ++ add(arr2.take(until - idx2), idx1 + shift1, Chain.empty))
138141
case (_, _ :: tl2) if isCommon2(idx2, lcs) =>
139142
// all values in arr1 were removed until the index of common value
140143
val until = lcs.head._1
@@ -144,18 +147,18 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
144147
shift1 - (until - idx1),
145148
idx2 + 1,
146149
lcs.tail,
147-
remove(idx1 + shift1, until - 1 + shift1, idx1 + shift1, arr1) reverse_::: acc)
150+
acc ++ remove(idx1 + shift1, until - 1 + shift1, idx1 + shift1, arr1))
148151
case (v1 :: tl1, v2 :: tl2) =>
149152
// values are different, recursively compute the diff of these values
150-
loop(tl1, tl2, idx1 + 1, shift1, idx2 + 1, lcs, diff(v1, v2, path / (idx1 + shift1)) reverse_::: acc)
153+
loop(tl1, tl2, idx1 + 1, shift1, idx2 + 1, lcs, acc ++ diff(v1, v2, path / (idx1 + shift1)))
151154
case (_, Nil) =>
152155
// all subsequent values in arr1 were removed
153-
remove(idx1 + shift1, idx1 + arr1.size - 1 + shift1, idx1 + shift1, arr1) reverse_::: acc
156+
acc ++ remove(idx1 + shift1, idx1 + arr1.size - 1 + shift1, idx1 + shift1, arr1)
154157
case (Nil, _) =>
155158
// all subsequent value in arr2 were added
156-
arr2.map(Add(path / "-", _)) reverse_::: acc
159+
acc ++ Chain.fromSeq(arr2.map(Add(path / "-", _)))
157160
}
158161

159-
loop(arr1, arr2, 0, 0, 0, lcs, Nil).reverse
162+
loop(arr1, arr2, 0, 0, 0, lcs, Chain.empty)
160163
}
161164
}

0 commit comments

Comments
 (0)