Merge pull request #205 from jiahuif-forks/feature/merge-new-algorithm
new algorithm for structural merge
diff --git a/typed/merge.go b/typed/merge.go
index afec9f3..75244ef 100644
--- a/typed/merge.go
+++ b/typed/merge.go
@@ -17,8 +17,6 @@
package typed
import (
- "math"
-
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
@@ -170,74 +168,94 @@
if lhs != nil {
lLen = lhs.Length()
}
- out := make([]interface{}, 0, int(math.Max(float64(rLen), float64(lLen))))
+ outLen := lLen
+ if outLen < rLen {
+ outLen = rLen
+ }
+ out := make([]interface{}, 0, outLen)
- lhsOrder := make([]fieldpath.PathElement, 0, lLen)
+ rhsOrder, observedRHS, rhsErrs := w.indexListPathElements(t, rhs)
+ errs = append(errs, rhsErrs...)
+ lhsOrder, observedLHS, lhsErrs := w.indexListPathElements(t, lhs)
+ errs = append(errs, lhsErrs...)
- // First, collect all LHS children.
- observedLHS := fieldpath.MakePathElementValueMap(lLen)
- if lhs != nil {
- for i := 0; i < lhs.Length(); i++ {
- child := lhs.At(i)
- pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
- if err != nil {
- errs = append(errs, errorf("lhs: element %v: %v", i, err.Error())...)
- // If we can't construct the path element, we can't
- // even report errors deeper in the schema, so bail on
- // this element.
- continue
- }
- if _, ok := observedLHS.Get(pe); ok {
- errs = append(errs, errorf("lhs: duplicate entries for key %v", pe.String())...)
- }
- observedLHS.Insert(pe, child)
- lhsOrder = append(lhsOrder, pe)
+ sharedOrder := make([]*fieldpath.PathElement, 0, rLen)
+ for i := range rhsOrder {
+ pe := &rhsOrder[i]
+ if _, ok := observedLHS.Get(*pe); ok {
+ sharedOrder = append(sharedOrder, pe)
}
}
- // Then merge with RHS children.
- observedRHS := fieldpath.MakePathElementSet(rLen)
- if rhs != nil {
- for i := 0; i < rhs.Length(); i++ {
- child := rhs.At(i)
- pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
- if err != nil {
- errs = append(errs, errorf("rhs: element %v: %v", i, err.Error())...)
- // If we can't construct the path element, we can't
- // even report errors deeper in the schema, so bail on
- // this element.
- continue
- }
- if observedRHS.Has(pe) {
- errs = append(errs, errorf("rhs: duplicate entries for key %v", pe.String())...)
- continue
- }
- observedRHS.Insert(pe)
- w2 := w.prepareDescent(pe, t.ElementType)
- w2.rhs = child
- if lchild, ok := observedLHS.Get(pe); ok {
- w2.lhs = lchild
- }
- errs = append(errs, w2.merge(pe.String)...)
- if w2.out != nil {
- out = append(out, *w2.out)
- }
- w.finishDescent(w2)
- }
+ var nextShared *fieldpath.PathElement
+ if len(sharedOrder) > 0 {
+ nextShared = sharedOrder[0]
+ sharedOrder = sharedOrder[1:]
}
- for _, pe := range lhsOrder {
- if observedRHS.Has(pe) {
- continue
+ lLen, rLen = len(lhsOrder), len(rhsOrder)
+ for lI, rI := 0, 0; lI < lLen || rI < rLen; {
+ if lI < lLen && rI < rLen {
+ pe := lhsOrder[lI]
+ if pe.Equals(rhsOrder[rI]) {
+ // merge LHS & RHS items
+ lChild, _ := observedLHS.Get(pe)
+ rChild, _ := observedRHS.Get(pe)
+ mergeOut, errs := w.mergeListItem(t, pe, lChild, rChild)
+ errs = append(errs, errs...)
+ if mergeOut != nil {
+ out = append(out, *mergeOut)
+ }
+ lI++
+ rI++
+
+ nextShared = nil
+ if len(sharedOrder) > 0 {
+ nextShared = sharedOrder[0]
+ sharedOrder = sharedOrder[1:]
+ }
+ continue
+ }
+ if _, ok := observedRHS.Get(pe); ok && nextShared != nil && !nextShared.Equals(lhsOrder[lI]) {
+ // shared item, but not the one we want in this round
+ lI++
+ continue
+ }
}
- value, _ := observedLHS.Get(pe)
- w2 := w.prepareDescent(pe, t.ElementType)
- w2.lhs = value
- errs = append(errs, w2.merge(pe.String)...)
- if w2.out != nil {
- out = append(out, *w2.out)
+ if lI < lLen {
+ pe := lhsOrder[lI]
+ if _, ok := observedRHS.Get(pe); !ok {
+ // take LHS item
+ lChild, _ := observedLHS.Get(pe)
+ mergeOut, errs := w.mergeListItem(t, pe, lChild, nil)
+ errs = append(errs, errs...)
+ if mergeOut != nil {
+ out = append(out, *mergeOut)
+ }
+ lI++
+ continue
+ }
}
- w.finishDescent(w2)
+ if rI < rLen {
+ // Take the RHS item, merge with matching LHS item if possible
+ pe := rhsOrder[rI]
+ lChild, _ := observedLHS.Get(pe) // may be nil
+ rChild, _ := observedRHS.Get(pe)
+ mergeOut, errs := w.mergeListItem(t, pe, lChild, rChild)
+ errs = append(errs, errs...)
+ if mergeOut != nil {
+ out = append(out, *mergeOut)
+ }
+ rI++
+ // Advance nextShared, if we are merging nextShared.
+ if nextShared != nil && nextShared.Equals(pe) {
+ nextShared = nil
+ if len(sharedOrder) > 0 {
+ nextShared = sharedOrder[0]
+ sharedOrder = sharedOrder[1:]
+ }
+ }
+ }
}
if len(out) > 0 {
@@ -248,6 +266,46 @@
return errs
}
+func (w *mergingWalker) indexListPathElements(t *schema.List, list value.List) ([]fieldpath.PathElement, fieldpath.PathElementValueMap, ValidationErrors) {
+ var errs ValidationErrors
+ length := 0
+ if list != nil {
+ length = list.Length()
+ }
+ observed := fieldpath.MakePathElementValueMap(length)
+ pes := make([]fieldpath.PathElement, 0, length)
+ for i := 0; i < length; i++ {
+ child := list.At(i)
+ pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
+ if err != nil {
+ errs = append(errs, errorf("element %v: %v", i, err.Error())...)
+ // If we can't construct the path element, we can't
+ // even report errors deeper in the schema, so bail on
+ // this element.
+ continue
+ }
+ if _, found := observed.Get(pe); found {
+ errs = append(errs, errorf("duplicate entries for key %v", pe.String())...)
+ continue
+ }
+ observed.Insert(pe, child)
+ pes = append(pes, pe)
+ }
+ return pes, observed, errs
+}
+
+func (w *mergingWalker) mergeListItem(t *schema.List, pe fieldpath.PathElement, lChild, rChild value.Value) (out *interface{}, errs ValidationErrors) {
+ w2 := w.prepareDescent(pe, t.ElementType)
+ w2.lhs = lChild
+ w2.rhs = rChild
+ errs = append(errs, w2.merge(pe.String)...)
+ if w2.out != nil {
+ out = w2.out
+ }
+ w.finishDescent(w2)
+ return
+}
+
func (w *mergingWalker) derefList(prefix string, v value.Value) (value.List, ValidationErrors) {
if v == nil {
return nil, nil
diff --git a/typed/merge_test.go b/typed/merge_test.go
index bb1e0a3..63745cd 100644
--- a/typed/merge_test.go
+++ b/typed/merge_test.go
@@ -281,14 +281,63 @@
`{"setStr":["a","b","c"]}`,
`{"setStr":["a","b","c"]}`,
}, {
+ `{"setStr":["a","b"]}`,
+ `{"setStr":["b","a"]}`,
+ `{"setStr":["b","a"]}`,
+ }, {
+ `{"setStr":["a","b","c"]}`,
+ `{"setStr":["d","e","f"]}`,
+ `{"setStr":["a","b","c","d","e","f"]}`,
+ }, {
+ `{"setStr":["a","b","c"]}`,
+ `{"setStr":["c","d","e","f"]}`,
+ `{"setStr":["a","b","c","d","e","f"]}`,
+ }, {
+ `{"setStr":["a","b","c","g","f"]}`,
+ `{"setStr":["c","d","e","f"]}`,
+ `{"setStr":["a","b","c","g","d","e","f"]}`,
+ }, {
+ `{"setStr":["a","b","c"]}`,
+ `{"setStr":["d","e","f","x","y","z"]}`,
+ `{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
+ }, {
+ `{"setStr":["c","d","e","f"]}`,
+ `{"setStr":["a","c","e"]}`,
+ `{"setStr":["a","c","d","e","f"]}`,
+ }, {
+ `{"setStr":["a","b","c","x","y","z"]}`,
+ `{"setStr":["d","e","f"]}`,
+ `{"setStr":["a","b","c","x","y","z","d","e","f"]}`,
+ }, {
+ `{"setStr":["a","b","c","x","y","z"]}`,
+ `{"setStr":["d","e","f","x","y","z"]}`,
+ `{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
+ }, {
+ `{"setStr":["c","a","g","f"]}`,
+ `{"setStr":["c","f","a","g"]}`,
+ `{"setStr":["c","f","a","g"]}`,
+ }, {
+ `{"setStr":["a","b","c","d"]}`,
+ `{"setStr":["d","e","f","a"]}`,
+ `{"setStr":["b","c","d","e","f","a"]}`,
+ }, {
+ `{"setStr":["c","d","e","f","g","h","i","j"]}`,
+ `{"setStr":["2","h","3","e","4","k","l"]}`,
+ `{"setStr":["c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
+ }, {
+ `{"setStr":["a","b","c","d","e","f","g","h","i","j"]}`,
+ `{"setStr":["1","b","2","h","3","e","4","k","l"]}`,
+ `{"setStr":["a","1","b","c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
+ }, {
`{"setBool":[true]}`,
`{"setBool":[false]}`,
- `{"setBool":[false,true]}`,
+ `{"setBool":[true, false]}`,
}, {
`{"setNumeric":[1,2,3.14159]}`,
`{"setNumeric":[1,2,3]}`,
- `{"setNumeric":[1,2,3,3.14159]}`,
- }},
+ `{"setNumeric":[1,2,3.14159,3]}`,
+ },
+ },
}, {
name: "associative list",
rootTypeName: "myRoot",
@@ -345,11 +394,11 @@
}, {
`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
`{"list":[{"key":"a","id":2,"value":{"a":"a"}}]}`,
- `{"list":[{"key":"a","id":2,"value":{"a":"a"}},{"key":"a","id":1,"value":{"a":"a"}}]}`,
+ `{"list":[{"key":"a","id":1,"value":{"a":"a"}},{"key":"a","id":2,"value":{"a":"a"}}]}`,
}, {
`{"list":[{"key":"a","id":1},{"key":"b","id":1}]}`,
`{"list":[{"key":"a","id":1},{"key":"a","id":2}]}`,
- `{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`,
+ `{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":2}]}`,
}, {
`{"list":[{"key":"b","id":2}]}`,
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
@@ -357,11 +406,11 @@
}, {
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
`{"list":[{"key":"c","id":3},{"key":"b","id":2}]}`,
- `{"list":[{"key":"c","id":3},{"key":"b","id":2},{"key":"a","id":1}]}`,
+ `{"list":[{"key":"a","id":1},{"key":"c","id":3},{"key":"b","id":2}]}`,
}, {
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
`{"list":[{"key":"c","id":3},{"key":"a","id":1}]}`,
- `{"list":[{"key":"c","id":3},{"key":"a","id":1},{"key":"b","id":2}]}`,
+ `{"list":[{"key":"b","id":2},{"key":"c","id":3},{"key":"a","id":1}]}`,
}, {
`{"atomicList":["a","a","a"]}`,
`{"atomicList":null}`,