Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb6a472

Browse files
authored
chore: implement generalized symmetric difference for set comparison (coder#14407)
* chore: implement generalized symmetric difference for set comparison Going to be used in Organization Sync + maybe group sync. Felt better to reuse, rather than copy
1 parent 4bd7fe8 commit cb6a472

File tree

4 files changed

+156
-23
lines changed

4 files changed

+156
-23
lines changed

coderd/rbac/roles.go

+3-23
Original file line numberDiff line numberDiff line change
@@ -764,29 +764,9 @@ func SiteRoles() []Role {
764764
// RBAC checks can be applied using "ActionCreate" and "ActionDelete" for
765765
// "added" and "removed" roles respectively.
766766
func ChangeRoleSet(from []RoleIdentifier, to []RoleIdentifier) (added []RoleIdentifier, removed []RoleIdentifier) {
767-
has := make(map[RoleIdentifier]struct{})
768-
for _, exists := range from {
769-
has[exists] = struct{}{}
770-
}
771-
772-
for _, roleName := range to {
773-
// If the user already has the role assigned, we don't need to check the permission
774-
// to reassign it. Only run permission checks on the difference in the set of
775-
// roles.
776-
if _, ok := has[roleName]; ok {
777-
delete(has, roleName)
778-
continue
779-
}
780-
781-
added = append(added, roleName)
782-
}
783-
784-
// Remaining roles are the ones removed/deleted.
785-
for roleName := range has {
786-
removed = append(removed, roleName)
787-
}
788-
789-
return added, removed
767+
return slice.SymmetricDifferenceFunc(from, to, func(a, b RoleIdentifier) bool {
768+
return a.Name == b.Name && a.OrganizationID == b.OrganizationID
769+
})
790770
}
791771

792772
// Permissions is just a helper function to make building roles that list out resources

coderd/util/slice/example_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package slice_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/coder/coder/v2/coderd/util/slice"
7+
)
8+
9+
//nolint:revive // They want me to error check my Printlns
10+
func ExampleSymmetricDifference() {
11+
// The goal of this function is to find the elements to add & remove from
12+
// set 'a' to make it equal to set 'b'.
13+
a := []int{1, 2, 5, 6}
14+
b := []int{2, 3, 4, 5}
15+
add, remove := slice.SymmetricDifference(a, b)
16+
fmt.Println("Elements to add:", add)
17+
fmt.Println("Elements to remove:", remove)
18+
// Output:
19+
// Elements to add: [3 4]
20+
// Elements to remove: [1 6]
21+
}

coderd/util/slice/slice.go

+32
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,35 @@ func Ascending[T constraints.Ordered](a, b T) int {
107107
func Descending[T constraints.Ordered](a, b T) int {
108108
return -Ascending[T](a, b)
109109
}
110+
111+
// SymmetricDifference returns the elements that need to be added and removed
112+
// to get from set 'a' to set 'b'.
113+
// In classical set theory notation, SymmetricDifference returns
114+
// all elements of {add} and {remove} together. It is more useful to
115+
// return them as their own slices.
116+
// Notation: A Δ B = (A\B) ∪ (B\A)
117+
// Example:
118+
//
119+
// a := []int{1, 3, 4}
120+
// b := []int{1, 2}
121+
// add, remove := SymmetricDifference(a, b)
122+
// fmt.Println(add) // [2]
123+
// fmt.Println(remove) // [3, 4]
124+
func SymmetricDifference[T comparable](a, b []T) (add []T, remove []T) {
125+
f := func(a, b T) bool { return a == b }
126+
return SymmetricDifferenceFunc(a, b, f)
127+
}
128+
129+
func SymmetricDifferenceFunc[T any](a, b []T, equal func(a, b T) bool) (add []T, remove []T) {
130+
return DifferenceFunc(b, a, equal), DifferenceFunc(a, b, equal)
131+
}
132+
133+
func DifferenceFunc[T any](a []T, b []T, equal func(a, b T) bool) []T {
134+
tmp := make([]T, 0, len(a))
135+
for _, v := range a {
136+
if !ContainsCompare(b, v, equal) {
137+
tmp = append(tmp, v)
138+
}
139+
}
140+
return tmp
141+
}

coderd/util/slice/slice_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,103 @@ func TestOmit(t *testing.T) {
131131
slice.Omit([]string{"a", "b", "c", "d", "e", "f"}, "c", "d", "e"),
132132
)
133133
}
134+
135+
func TestSymmetricDifference(t *testing.T) {
136+
t.Parallel()
137+
138+
t.Run("Simple", func(t *testing.T) {
139+
t.Parallel()
140+
141+
add, remove := slice.SymmetricDifference([]int{1, 3, 4}, []int{1, 2})
142+
require.ElementsMatch(t, []int{2}, add)
143+
require.ElementsMatch(t, []int{3, 4}, remove)
144+
})
145+
146+
t.Run("Large", func(t *testing.T) {
147+
t.Parallel()
148+
149+
add, remove := slice.SymmetricDifference(
150+
[]int{1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15},
151+
[]int{1, 3, 7, 9, 11, 13, 17},
152+
)
153+
require.ElementsMatch(t, []int{7, 9, 17}, add)
154+
require.ElementsMatch(t, []int{2, 4, 5, 10, 12, 14, 15}, remove)
155+
})
156+
157+
t.Run("AddOnly", func(t *testing.T) {
158+
t.Parallel()
159+
160+
add, remove := slice.SymmetricDifference(
161+
[]int{1, 2},
162+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
163+
)
164+
require.ElementsMatch(t, []int{3, 4, 5, 6, 7, 8, 9}, add)
165+
require.ElementsMatch(t, []int{}, remove)
166+
})
167+
168+
t.Run("RemoveOnly", func(t *testing.T) {
169+
t.Parallel()
170+
171+
add, remove := slice.SymmetricDifference(
172+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
173+
[]int{1, 2},
174+
)
175+
require.ElementsMatch(t, []int{}, add)
176+
require.ElementsMatch(t, []int{3, 4, 5, 6, 7, 8, 9}, remove)
177+
})
178+
179+
t.Run("Equal", func(t *testing.T) {
180+
t.Parallel()
181+
182+
add, remove := slice.SymmetricDifference(
183+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
184+
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
185+
)
186+
require.ElementsMatch(t, []int{}, add)
187+
require.ElementsMatch(t, []int{}, remove)
188+
})
189+
190+
t.Run("ToEmpty", func(t *testing.T) {
191+
t.Parallel()
192+
193+
add, remove := slice.SymmetricDifference(
194+
[]int{1, 2, 3},
195+
[]int{},
196+
)
197+
require.ElementsMatch(t, []int{}, add)
198+
require.ElementsMatch(t, []int{1, 2, 3}, remove)
199+
})
200+
201+
t.Run("ToNil", func(t *testing.T) {
202+
t.Parallel()
203+
204+
add, remove := slice.SymmetricDifference(
205+
[]int{1, 2, 3},
206+
nil,
207+
)
208+
require.ElementsMatch(t, []int{}, add)
209+
require.ElementsMatch(t, []int{1, 2, 3}, remove)
210+
})
211+
212+
t.Run("FromEmpty", func(t *testing.T) {
213+
t.Parallel()
214+
215+
add, remove := slice.SymmetricDifference(
216+
[]int{},
217+
[]int{1, 2, 3},
218+
)
219+
require.ElementsMatch(t, []int{1, 2, 3}, add)
220+
require.ElementsMatch(t, []int{}, remove)
221+
})
222+
223+
t.Run("FromNil", func(t *testing.T) {
224+
t.Parallel()
225+
226+
add, remove := slice.SymmetricDifference(
227+
nil,
228+
[]int{1, 2, 3},
229+
)
230+
require.ElementsMatch(t, []int{1, 2, 3}, add)
231+
require.ElementsMatch(t, []int{}, remove)
232+
})
233+
}

0 commit comments

Comments
 (0)