Skip to content

Commit 4fa0d92

Browse files
committed
Optimize merge, make wildcards always take precedence
1 parent f395ded commit 4fa0d92

File tree

2 files changed

+74
-45
lines changed

2 files changed

+74
-45
lines changed

Diff for: fieldpath/set.go

+72-43
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func PrefixMatcher(parts ...interface{}) (*SetMatcher, error) {
182182
return nil, fmt.Errorf("unexpected type %T", t)
183183
}
184184
current = &SetMatcher{
185-
Members: []*SetMemberMatcher{{
185+
members: []*SetMemberMatcher{{
186186
Path: pattern,
187187
Child: current,
188188
}},
@@ -198,54 +198,61 @@ func MatchAnyPathElement() PathElementMatcher {
198198

199199
// MatchAnySet returns a SetMatcher that matches any set.
200200
func MatchAnySet() *SetMatcher {
201-
return &SetMatcher{Wildcard: true}
201+
return &SetMatcher{wildcard: true}
202+
}
203+
204+
// NewSetMatcher returns a new SetMatcher.
205+
// Wildcard members take precedent over non-wildcard members;
206+
// all non-wildcard members are ignored if there is a wildcard members.
207+
func NewSetMatcher(wildcard bool, members ...*SetMemberMatcher) *SetMatcher {
208+
sort.Sort(sortedMemberMatcher(members))
209+
return &SetMatcher{wildcard: wildcard, members: members}
202210
}
203211

204212
// SetMatcher defines a pattern that matches fields in a Set.
205213
// SetMatcher is structured much like a Set but with wildcard support.
206214
type SetMatcher struct {
207-
// Wildcard indicates that all members and children are included in the match.
208-
// If set, the Members field is ignored.
209-
Wildcard bool
210-
// Members provides patterns to match the members of a Set.
211-
Members []*SetMemberMatcher
215+
// wildcard indicates that all members and children are included in the match.
216+
// If set, the members field is ignored.
217+
wildcard bool
218+
// members provides patterns to match the members of a Set.
219+
// Wildcard members are sorted before non-wildcards and take precedent over
220+
// non-wildcard members.
221+
members sortedMemberMatcher
222+
}
223+
224+
type sortedMemberMatcher []*SetMemberMatcher
225+
226+
func (s sortedMemberMatcher) Len() int { return len(s) }
227+
func (s sortedMemberMatcher) Less(i, j int) bool { return s[i].Path.Less(s[j].Path) }
228+
func (s sortedMemberMatcher) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
229+
func (s sortedMemberMatcher) Find(p PathElementMatcher) (location int, ok bool) {
230+
return sort.Find(len(s), func(i int) int {
231+
return s[i].Path.Compare(p)
232+
})
212233
}
213234

214-
// Merge merges two SetMatchers into a single SetMatcher that matches the field paths of both SetMatchers.
215-
// During the merge, Members of s2 with the same PathElementMatcher as a member of s are merged into the member of s.
216-
// All other members of s2 are appended to the resulting member list in their original relative order.
217-
// When members are merged, the child SetMatchers are merged by calling this function recursively.
235+
// Merge merges s and s2 and returns a SetMatcher that matches all field paths matched by either s or s2.
236+
// During the merge, members of s and s2 with the same PathElementMatcher merged into a single member
237+
// with the children of each merged by calling this function recursively.
218238
func (s *SetMatcher) Merge(s2 *SetMatcher) *SetMatcher {
219-
if s.Wildcard || s2.Wildcard {
220-
return &SetMatcher{Wildcard: true}
221-
}
222-
223-
// TODO: Optimize. This is O(n^2). In practice, usually a single member is being inserted at a time,
224-
// but that's still O(n). Sorting would help a ton.
225-
var unionedMembers []*SetMemberMatcher
226-
for _, m := range s.Members {
227-
for _, m2 := range s2.Members {
228-
if m.Path.PathElement.Equals(m2.Path.PathElement) && m.Path.Wildcard == m2.Path.Wildcard {
229-
unionedMembers = append(unionedMembers, &SetMemberMatcher{
230-
Path: m.Path,
231-
Child: m.Child.Merge(m2.Child),
232-
})
233-
} else {
234-
unionedMembers = append(unionedMembers, m)
235-
}
236-
}
237-
}
238-
for _, m2 := range s2.Members {
239-
for _, existing := range unionedMembers {
240-
if !m2.Path.PathElement.Equals(existing.Path.PathElement) {
241-
unionedMembers = append(unionedMembers, m2)
239+
if s.wildcard || s2.wildcard {
240+
return NewSetMatcher(true)
241+
}
242+
merged := make(sortedMemberMatcher, len(s.members), len(s.members)+len(s2.members))
243+
copy(merged, s.members)
244+
for _, m := range s2.members {
245+
if i, ok := s.members.Find(m.Path); ok {
246+
// since merged is a shallow copy, do not modify elements in place
247+
merged[i] = &SetMemberMatcher{
248+
Path: merged[i].Path,
249+
Child: merged[i].Child.Merge(m.Child),
242250
}
251+
} else {
252+
merged = append(merged, m)
243253
}
244254
}
245-
246-
return &SetMatcher{
247-
Members: unionedMembers,
248-
}
255+
return NewSetMatcher(false, merged...) // sort happens here
249256
}
250257

251258
// SetMemberMatcher defines a pattern that matches the members of a Set.
@@ -274,15 +281,37 @@ type PathElementMatcher struct {
274281
PathElement
275282
}
276283

284+
func (p PathElementMatcher) Equals(p2 PathElementMatcher) bool {
285+
return p.Wildcard != p2.Wildcard && p.PathElement.Equals(p2.PathElement)
286+
}
287+
288+
func (p PathElementMatcher) Less(p2 PathElementMatcher) bool {
289+
if p.Wildcard && !p2.Wildcard {
290+
return true
291+
} else if p2.Wildcard {
292+
return false
293+
}
294+
return p.PathElement.Less(p2.PathElement)
295+
}
296+
297+
func (p PathElementMatcher) Compare(p2 PathElementMatcher) int {
298+
if p.Wildcard && !p2.Wildcard {
299+
return -1
300+
} else if p2.Wildcard {
301+
return 1
302+
}
303+
return p.PathElement.Compare(p2.PathElement)
304+
}
305+
277306
// FilterIncludeMatches returns a Set with only the field paths that match.
278307
func (s *Set) FilterIncludeMatches(pattern *SetMatcher) *Set {
279-
if pattern.Wildcard {
308+
if pattern.wildcard {
280309
return s
281310
}
282311

283312
members := PathElementSet{}
284313
for _, m := range s.Members.members {
285-
for _, pm := range pattern.Members {
314+
for _, pm := range pattern.members {
286315
if pm.Path.Wildcard || pm.Path.PathElement.Equals(m) {
287316
members.Insert(m)
288317
break
@@ -637,13 +666,13 @@ func (s *SetNodeMap) EnsureNamedFieldsAreMembers(sc *schema.Schema, tr schema.Ty
637666

638667
// FilterIncludeMatches returns a SetNodeMap with only the field paths that match the pattern.
639668
func (s *SetNodeMap) FilterIncludeMatches(pattern *SetMatcher) *SetNodeMap {
640-
if pattern.Wildcard {
669+
if pattern.wildcard {
641670
return s
642671
}
643672

644673
var out sortedSetNode
645674
for _, member := range s.members {
646-
for _, c := range pattern.Members {
675+
for _, c := range pattern.members {
647676
if c.Path.Wildcard || c.Path.PathElement.Equals(member.pathElement) {
648677
childSet := member.set.FilterIncludeMatches(c.Child)
649678
if childSet.Size() > 0 {
@@ -727,7 +756,7 @@ func (t excludeFilter) Filter(set *Set) *Set {
727756
// PrefixMatcher and MakePrefixMatcherOrDie can help create basic SetPatterns.
728757
func NewIncludeMatcherFilter(patterns ...*SetMatcher) Filter {
729758
if len(patterns) == 0 {
730-
return includeMatcherFilter{&SetMatcher{Wildcard: true}}
759+
return includeMatcherFilter{&SetMatcher{wildcard: true}}
731760
}
732761
pattern := patterns[0]
733762
for i := 1; i < len(patterns); i++ {

Diff for: fieldpath/set_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -988,8 +988,8 @@ func TestFilterByPattern(t *testing.T) {
988988
MakePathOrDie("spec", "list", 2, "f2"),
989989
),
990990
filter: NewIncludeMatcherFilter(
991-
MakePrefixMatcherOrDie("spec", "list", MatchAnyPathElement(), "f1"), // wildcard matches first and runs
992-
MakePrefixMatcherOrDie("spec", "list", 1, "f2"), // already matched by wildcard so this is ignored
991+
MakePrefixMatcherOrDie("spec", "list", MatchAnyPathElement(), "f1"), // takes precedence
992+
MakePrefixMatcherOrDie("spec", "list", 1, "f2"), // ignored
993993
),
994994
expect: NewSet(
995995
MakePathOrDie("spec", "list", 0, "f1"),

0 commit comments

Comments
 (0)