forked from kubernetes-sigs/cli-utils
-
Notifications
You must be signed in to change notification settings - Fork 3
/
objmetadata_set.go
211 lines (195 loc) · 5.52 KB
/
objmetadata_set.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package object
import (
"hash/fnv"
"sort"
"strconv"
)
// ObjMetadataSet is an ordered list of ObjMetadata that acts like an unordered
// set for comparison purposes.
type ObjMetadataSet []ObjMetadata
// UnstructuredSetEquals returns true if the slice of objects in setA equals
// the slice of objects in setB.
func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB))
}
// ObjMetadataSetFromMap constructs a set from a map
func ObjMetadataSetFromMap(mapA map[ObjMetadata]struct{}) ObjMetadataSet {
setA := make(ObjMetadataSet, 0, len(mapA))
for f := range mapA {
setA = append(setA, f)
}
return setA
}
// Equal returns true if the two sets contain equivalent objects. Duplicates are
// ignored.
// This function satisfies the cmp.Equal interface from github.com/google/go-cmp
func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool {
mapA := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
mapA[a] = struct{}{}
}
mapB := make(map[ObjMetadata]struct{}, len(setB))
for _, b := range setB {
mapB[b] = struct{}{}
}
if len(mapA) != len(mapB) {
return false
}
for b := range mapB {
if _, exists := mapA[b]; !exists {
return false
}
}
return true
}
// Contains checks if the provided ObjMetadata exists in the set.
func (setA ObjMetadataSet) Contains(id ObjMetadata) bool {
for _, om := range setA {
if om == id {
return true
}
}
return false
}
// Remove the object from the set and return the updated set.
func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet {
for i, a := range setA {
if a == obj {
setA[len(setA)-1], setA[i] = setA[i], setA[len(setA)-1]
return setA[:len(setA)-1]
}
}
return setA
}
// Intersection returns the set of unique objects in both set A and set B.
func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet {
var maxlen int
if len(setA) > len(setB) {
maxlen = len(setA)
} else {
maxlen = len(setB)
}
mapI := make(map[ObjMetadata]struct{}, maxlen)
mapB := setB.ToMap()
for _, a := range setA {
if _, ok := mapB[a]; ok {
mapI[a] = struct{}{}
}
}
intersection := make(ObjMetadataSet, 0, len(mapI))
// Iterate over setA & setB to retain input order and have stable output
for _, id := range setA {
if _, ok := mapI[id]; ok {
intersection = append(intersection, id)
delete(mapI, id)
}
}
for _, id := range setB {
if _, ok := mapI[id]; ok {
intersection = append(intersection, id)
delete(mapI, id)
}
}
return intersection
}
// Union returns the set of unique objects from the merging of set A and set B.
func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet {
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
for _, a := range setA {
m[a] = struct{}{}
}
for _, b := range setB {
m[b] = struct{}{}
}
union := make(ObjMetadataSet, 0, len(m))
// Iterate over setA & setB to retain input order and have stable output
for _, id := range setA {
if _, ok := m[id]; ok {
union = append(union, id)
delete(m, id)
}
}
for _, id := range setB {
if _, ok := m[id]; ok {
union = append(union, id)
delete(m, id)
}
}
return union
}
// Diff returns the set of objects that exist in set A, but not in set B (A - B).
func (setA ObjMetadataSet) Diff(setB ObjMetadataSet) ObjMetadataSet {
// Create a map of the elements of A
m := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
m[a] = struct{}{}
}
// Remove from A each element of B
for _, b := range setB {
delete(m, b) // OK to delete even if b not in m
}
// Create/return slice from the map of remaining items
diff := make(ObjMetadataSet, 0, len(m))
// Iterate over setA to retain input order and have stable output
for _, id := range setA {
if _, ok := m[id]; ok {
diff = append(diff, id)
delete(m, id)
}
}
return diff
}
// Unique returns the set with duplicates removed.
// Order may or may not remain consistent.
func (setA ObjMetadataSet) Unique() ObjMetadataSet {
return ObjMetadataSetFromMap(setA.ToMap())
}
// Hash the objects in the set by serializing, sorting, concatonating, and
// hashing the result with the 32-bit FNV-1a algorithm.
func (setA ObjMetadataSet) Hash() string {
objStrs := make([]string, 0, len(setA))
for _, obj := range setA {
objStrs = append(objStrs, obj.String())
}
sort.Strings(objStrs)
h := fnv.New32a()
for _, obj := range objStrs {
// Hash32.Write never returns an error
// https://pkg.go.dev/hash#pkg-types
_, _ = h.Write([]byte(obj))
}
return strconv.FormatUint(uint64(h.Sum32()), 16)
}
// ToMap returns the set as a map, with objMeta keys and empty struct values.
func (setA ObjMetadataSet) ToMap() map[ObjMetadata]struct{} {
m := make(map[ObjMetadata]struct{}, len(setA))
for _, objMeta := range setA {
m[objMeta] = struct{}{}
}
return m
}
// ToStringMap returns the set as a serializable map, with objMeta keys and
// empty string values.
func (setA ObjMetadataSet) ToStringMap() map[string]string {
stringMap := make(map[string]string, len(setA))
for _, objMeta := range setA {
stringMap[objMeta.String()] = ""
}
return stringMap
}
// FromStringMap returns a set from a serializable map, with objMeta keys and
// empty string values. Errors if parsing fails.
func FromStringMap(in map[string]string) (ObjMetadataSet, error) {
var set ObjMetadataSet
for s := range in {
objMeta, err := ParseObjMetadata(s)
if err != nil {
return nil, err
}
set = append(set, objMeta)
}
return set, nil
}