Skip to content

Commit 65611e5

Browse files
authored
Merge pull request #205 from jiahuif-forks/feature/merge-new-algorithm
new algorithm for structural merge
2 parents 5bc02fb + 19f7404 commit 65611e5

File tree

2 files changed

+173
-66
lines changed

2 files changed

+173
-66
lines changed

typed/merge.go

Lines changed: 117 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ limitations under the License.
1717
package typed
1818

1919
import (
20-
"math"
21-
2220
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
2321
"sigs.k8s.io/structured-merge-diff/v4/schema"
2422
"sigs.k8s.io/structured-merge-diff/v4/value"
@@ -170,74 +168,94 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
170168
if lhs != nil {
171169
lLen = lhs.Length()
172170
}
173-
out := make([]interface{}, 0, int(math.Max(float64(rLen), float64(lLen))))
171+
outLen := lLen
172+
if outLen < rLen {
173+
outLen = rLen
174+
}
175+
out := make([]interface{}, 0, outLen)
176+
177+
rhsOrder, observedRHS, rhsErrs := w.indexListPathElements(t, rhs)
178+
errs = append(errs, rhsErrs...)
179+
lhsOrder, observedLHS, lhsErrs := w.indexListPathElements(t, lhs)
180+
errs = append(errs, lhsErrs...)
181+
182+
sharedOrder := make([]*fieldpath.PathElement, 0, rLen)
183+
for i := range rhsOrder {
184+
pe := &rhsOrder[i]
185+
if _, ok := observedLHS.Get(*pe); ok {
186+
sharedOrder = append(sharedOrder, pe)
187+
}
188+
}
174189

175-
lhsOrder := make([]fieldpath.PathElement, 0, lLen)
190+
var nextShared *fieldpath.PathElement
191+
if len(sharedOrder) > 0 {
192+
nextShared = sharedOrder[0]
193+
sharedOrder = sharedOrder[1:]
194+
}
176195

177-
// First, collect all LHS children.
178-
observedLHS := fieldpath.MakePathElementValueMap(lLen)
179-
if lhs != nil {
180-
for i := 0; i < lhs.Length(); i++ {
181-
child := lhs.At(i)
182-
pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
183-
if err != nil {
184-
errs = append(errs, errorf("lhs: element %v: %v", i, err.Error())...)
185-
// If we can't construct the path element, we can't
186-
// even report errors deeper in the schema, so bail on
187-
// this element.
196+
lLen, rLen = len(lhsOrder), len(rhsOrder)
197+
for lI, rI := 0, 0; lI < lLen || rI < rLen; {
198+
if lI < lLen && rI < rLen {
199+
pe := lhsOrder[lI]
200+
if pe.Equals(rhsOrder[rI]) {
201+
// merge LHS & RHS items
202+
lChild, _ := observedLHS.Get(pe)
203+
rChild, _ := observedRHS.Get(pe)
204+
mergeOut, errs := w.mergeListItem(t, pe, lChild, rChild)
205+
errs = append(errs, errs...)
206+
if mergeOut != nil {
207+
out = append(out, *mergeOut)
208+
}
209+
lI++
210+
rI++
211+
212+
nextShared = nil
213+
if len(sharedOrder) > 0 {
214+
nextShared = sharedOrder[0]
215+
sharedOrder = sharedOrder[1:]
216+
}
188217
continue
189218
}
190-
if _, ok := observedLHS.Get(pe); ok {
191-
errs = append(errs, errorf("lhs: duplicate entries for key %v", pe.String())...)
192-
}
193-
observedLHS.Insert(pe, child)
194-
lhsOrder = append(lhsOrder, pe)
195-
}
196-
}
197-
198-
// Then merge with RHS children.
199-
observedRHS := fieldpath.MakePathElementSet(rLen)
200-
if rhs != nil {
201-
for i := 0; i < rhs.Length(); i++ {
202-
child := rhs.At(i)
203-
pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
204-
if err != nil {
205-
errs = append(errs, errorf("rhs: element %v: %v", i, err.Error())...)
206-
// If we can't construct the path element, we can't
207-
// even report errors deeper in the schema, so bail on
208-
// this element.
219+
if _, ok := observedRHS.Get(pe); ok && nextShared != nil && !nextShared.Equals(lhsOrder[lI]) {
220+
// shared item, but not the one we want in this round
221+
lI++
209222
continue
210223
}
211-
if observedRHS.Has(pe) {
212-
errs = append(errs, errorf("rhs: duplicate entries for key %v", pe.String())...)
224+
}
225+
if lI < lLen {
226+
pe := lhsOrder[lI]
227+
if _, ok := observedRHS.Get(pe); !ok {
228+
// take LHS item
229+
lChild, _ := observedLHS.Get(pe)
230+
mergeOut, errs := w.mergeListItem(t, pe, lChild, nil)
231+
errs = append(errs, errs...)
232+
if mergeOut != nil {
233+
out = append(out, *mergeOut)
234+
}
235+
lI++
213236
continue
214237
}
215-
observedRHS.Insert(pe)
216-
w2 := w.prepareDescent(pe, t.ElementType)
217-
w2.rhs = child
218-
if lchild, ok := observedLHS.Get(pe); ok {
219-
w2.lhs = lchild
238+
}
239+
if rI < rLen {
240+
// Take the RHS item, merge with matching LHS item if possible
241+
pe := rhsOrder[rI]
242+
lChild, _ := observedLHS.Get(pe) // may be nil
243+
rChild, _ := observedRHS.Get(pe)
244+
mergeOut, errs := w.mergeListItem(t, pe, lChild, rChild)
245+
errs = append(errs, errs...)
246+
if mergeOut != nil {
247+
out = append(out, *mergeOut)
220248
}
221-
errs = append(errs, w2.merge(pe.String)...)
222-
if w2.out != nil {
223-
out = append(out, *w2.out)
249+
rI++
250+
// Advance nextShared, if we are merging nextShared.
251+
if nextShared != nil && nextShared.Equals(pe) {
252+
nextShared = nil
253+
if len(sharedOrder) > 0 {
254+
nextShared = sharedOrder[0]
255+
sharedOrder = sharedOrder[1:]
256+
}
224257
}
225-
w.finishDescent(w2)
226-
}
227-
}
228-
229-
for _, pe := range lhsOrder {
230-
if observedRHS.Has(pe) {
231-
continue
232-
}
233-
value, _ := observedLHS.Get(pe)
234-
w2 := w.prepareDescent(pe, t.ElementType)
235-
w2.lhs = value
236-
errs = append(errs, w2.merge(pe.String)...)
237-
if w2.out != nil {
238-
out = append(out, *w2.out)
239258
}
240-
w.finishDescent(w2)
241259
}
242260

243261
if len(out) > 0 {
@@ -248,6 +266,46 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
248266
return errs
249267
}
250268

269+
func (w *mergingWalker) indexListPathElements(t *schema.List, list value.List) ([]fieldpath.PathElement, fieldpath.PathElementValueMap, ValidationErrors) {
270+
var errs ValidationErrors
271+
length := 0
272+
if list != nil {
273+
length = list.Length()
274+
}
275+
observed := fieldpath.MakePathElementValueMap(length)
276+
pes := make([]fieldpath.PathElement, 0, length)
277+
for i := 0; i < length; i++ {
278+
child := list.At(i)
279+
pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
280+
if err != nil {
281+
errs = append(errs, errorf("element %v: %v", i, err.Error())...)
282+
// If we can't construct the path element, we can't
283+
// even report errors deeper in the schema, so bail on
284+
// this element.
285+
continue
286+
}
287+
if _, found := observed.Get(pe); found {
288+
errs = append(errs, errorf("duplicate entries for key %v", pe.String())...)
289+
continue
290+
}
291+
observed.Insert(pe, child)
292+
pes = append(pes, pe)
293+
}
294+
return pes, observed, errs
295+
}
296+
297+
func (w *mergingWalker) mergeListItem(t *schema.List, pe fieldpath.PathElement, lChild, rChild value.Value) (out *interface{}, errs ValidationErrors) {
298+
w2 := w.prepareDescent(pe, t.ElementType)
299+
w2.lhs = lChild
300+
w2.rhs = rChild
301+
errs = append(errs, w2.merge(pe.String)...)
302+
if w2.out != nil {
303+
out = w2.out
304+
}
305+
w.finishDescent(w2)
306+
return
307+
}
308+
251309
func (w *mergingWalker) derefList(prefix string, v value.Value) (value.List, ValidationErrors) {
252310
if v == nil {
253311
return nil, nil

typed/merge_test.go

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,15 +280,64 @@ var mergeCases = []mergeTestCase{{
280280
`{"setStr":[]}`,
281281
`{"setStr":["a","b","c"]}`,
282282
`{"setStr":["a","b","c"]}`,
283+
}, {
284+
`{"setStr":["a","b"]}`,
285+
`{"setStr":["b","a"]}`,
286+
`{"setStr":["b","a"]}`,
287+
}, {
288+
`{"setStr":["a","b","c"]}`,
289+
`{"setStr":["d","e","f"]}`,
290+
`{"setStr":["a","b","c","d","e","f"]}`,
291+
}, {
292+
`{"setStr":["a","b","c"]}`,
293+
`{"setStr":["c","d","e","f"]}`,
294+
`{"setStr":["a","b","c","d","e","f"]}`,
295+
}, {
296+
`{"setStr":["a","b","c","g","f"]}`,
297+
`{"setStr":["c","d","e","f"]}`,
298+
`{"setStr":["a","b","c","g","d","e","f"]}`,
299+
}, {
300+
`{"setStr":["a","b","c"]}`,
301+
`{"setStr":["d","e","f","x","y","z"]}`,
302+
`{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
303+
}, {
304+
`{"setStr":["c","d","e","f"]}`,
305+
`{"setStr":["a","c","e"]}`,
306+
`{"setStr":["a","c","d","e","f"]}`,
307+
}, {
308+
`{"setStr":["a","b","c","x","y","z"]}`,
309+
`{"setStr":["d","e","f"]}`,
310+
`{"setStr":["a","b","c","x","y","z","d","e","f"]}`,
311+
}, {
312+
`{"setStr":["a","b","c","x","y","z"]}`,
313+
`{"setStr":["d","e","f","x","y","z"]}`,
314+
`{"setStr":["a","b","c","d","e","f","x","y","z"]}`,
315+
}, {
316+
`{"setStr":["c","a","g","f"]}`,
317+
`{"setStr":["c","f","a","g"]}`,
318+
`{"setStr":["c","f","a","g"]}`,
319+
}, {
320+
`{"setStr":["a","b","c","d"]}`,
321+
`{"setStr":["d","e","f","a"]}`,
322+
`{"setStr":["b","c","d","e","f","a"]}`,
323+
}, {
324+
`{"setStr":["c","d","e","f","g","h","i","j"]}`,
325+
`{"setStr":["2","h","3","e","4","k","l"]}`,
326+
`{"setStr":["c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
327+
}, {
328+
`{"setStr":["a","b","c","d","e","f","g","h","i","j"]}`,
329+
`{"setStr":["1","b","2","h","3","e","4","k","l"]}`,
330+
`{"setStr":["a","1","b","c","d","f","g","2","h","i","j","3","e","4","k","l"]}`,
283331
}, {
284332
`{"setBool":[true]}`,
285333
`{"setBool":[false]}`,
286-
`{"setBool":[false,true]}`,
334+
`{"setBool":[true, false]}`,
287335
}, {
288336
`{"setNumeric":[1,2,3.14159]}`,
289337
`{"setNumeric":[1,2,3]}`,
290-
`{"setNumeric":[1,2,3,3.14159]}`,
291-
}},
338+
`{"setNumeric":[1,2,3.14159,3]}`,
339+
},
340+
},
292341
}, {
293342
name: "associative list",
294343
rootTypeName: "myRoot",
@@ -345,23 +394,23 @@ var mergeCases = []mergeTestCase{{
345394
}, {
346395
`{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
347396
`{"list":[{"key":"a","id":2,"value":{"a":"a"}}]}`,
348-
`{"list":[{"key":"a","id":2,"value":{"a":"a"}},{"key":"a","id":1,"value":{"a":"a"}}]}`,
397+
`{"list":[{"key":"a","id":1,"value":{"a":"a"}},{"key":"a","id":2,"value":{"a":"a"}}]}`,
349398
}, {
350399
`{"list":[{"key":"a","id":1},{"key":"b","id":1}]}`,
351400
`{"list":[{"key":"a","id":1},{"key":"a","id":2}]}`,
352-
`{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`,
401+
`{"list":[{"key":"a","id":1},{"key":"b","id":1},{"key":"a","id":2}]}`,
353402
}, {
354403
`{"list":[{"key":"b","id":2}]}`,
355404
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
356405
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
357406
}, {
358407
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
359408
`{"list":[{"key":"c","id":3},{"key":"b","id":2}]}`,
360-
`{"list":[{"key":"c","id":3},{"key":"b","id":2},{"key":"a","id":1}]}`,
409+
`{"list":[{"key":"a","id":1},{"key":"c","id":3},{"key":"b","id":2}]}`,
361410
}, {
362411
`{"list":[{"key":"a","id":1},{"key":"b","id":2},{"key":"c","id":3}]}`,
363412
`{"list":[{"key":"c","id":3},{"key":"a","id":1}]}`,
364-
`{"list":[{"key":"c","id":3},{"key":"a","id":1},{"key":"b","id":2}]}`,
413+
`{"list":[{"key":"b","id":2},{"key":"c","id":3},{"key":"a","id":1}]}`,
365414
}, {
366415
`{"atomicList":["a","a","a"]}`,
367416
`{"atomicList":null}`,

0 commit comments

Comments
 (0)