Skip to content

Commit 2ef7e76

Browse files
author
Han Kang
committed
add generic implementation of bidirectional map
furthermore, this PR extracts out the generic interfaces we use in the generic set implementation so that we can reuse them in the bidirectional map.
1 parent e7106e6 commit 2ef7e76

File tree

7 files changed

+195
-19
lines changed

7 files changed

+195
-19
lines changed

bidirectionalmap/OWNERS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# See the OWNERS docs at https://go.k8s.io/owners
2+
3+
reviewers:
4+
- logicalhan
5+
- thockin
6+
approvers:
7+
- logicalhan
8+
- thockin

bidirectionalmap/map.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package bidirectionalmap
18+
19+
import (
20+
"k8s.io/utils/genericinterfaces"
21+
"k8s.io/utils/set"
22+
)
23+
24+
// BidirectionalMap is a bidirectional map.
25+
type BidirectionalMap[X genericinterfaces.Ordered, Y genericinterfaces.Ordered] struct {
26+
right map[X]set.Set[Y]
27+
left map[Y]set.Set[X]
28+
}
29+
30+
// NewBidirectionalMap creates a new BidirectionalMap.
31+
func NewBidirectionalMap[X genericinterfaces.Ordered, Y genericinterfaces.Ordered]() *BidirectionalMap[X, Y] {
32+
return &BidirectionalMap[X, Y]{
33+
right: make(map[X]set.Set[Y]),
34+
left: make(map[Y]set.Set[X]),
35+
}
36+
}
37+
38+
// InsertRight inserts a new item into the right map.
39+
func (bdm *BidirectionalMap[X, Y]) InsertRight(x X, y Y) bool {
40+
if bdm.right[x] == nil {
41+
bdm.right[x] = set.New[Y]()
42+
}
43+
if bdm.right[x].Has(y) {
44+
return false
45+
}
46+
if bdm.left[y] == nil {
47+
bdm.left[y] = set.New[X]()
48+
}
49+
bdm.right[x].Insert(y)
50+
bdm.left[y].Insert(x)
51+
return true
52+
}
53+
54+
// InsertLeft inserts a new item into the left map.
55+
func (bdm *BidirectionalMap[X, Y]) InsertLeft(y Y, x X) bool {
56+
if bdm.left[y] == nil {
57+
bdm.left[y] = set.New[X]()
58+
}
59+
if bdm.left[y].Has(x) {
60+
return false
61+
}
62+
if bdm.right[x] == nil {
63+
bdm.right[x] = set.New[Y]()
64+
}
65+
bdm.right[x].Insert(y)
66+
bdm.left[y].Insert(x)
67+
return true
68+
}
69+
70+
// GetRight returns a value from the right map.
71+
func (bdm *BidirectionalMap[X, Y]) GetRight(x X) set.Set[Y] {
72+
return bdm.right[x]
73+
}
74+
75+
// GetLeft returns a value from left map.
76+
func (bdm *BidirectionalMap[X, Y]) GetLeft(y Y) set.Set[X] {
77+
return bdm.left[y]
78+
}
79+
80+
// DeleteRightKey deletes the key from the right map and removes
81+
// the inverse mapping from the left map.
82+
func (bdm *BidirectionalMap[X, Y]) DeleteRightKey(x X) {
83+
if leftValues, ok := bdm.right[x]; ok {
84+
delete(bdm.right, x)
85+
for y := range leftValues {
86+
bdm.left[y].Delete(x)
87+
}
88+
}
89+
}
90+
91+
// DeleteLeftKey deletes the key from the left map and removes
92+
// the inverse mapping from the right map.
93+
func (bdm *BidirectionalMap[X, Y]) DeleteLeftKey(y Y) {
94+
if rightValues, ok := bdm.left[y]; ok {
95+
delete(bdm.left, y)
96+
for x := range rightValues {
97+
bdm.right[x].Delete(y)
98+
}
99+
}
100+
}
101+
102+
// GetRightKeys returns the keys from the right map.
103+
func (bdm *BidirectionalMap[X, Y]) GetRightKeys() set.Set[X] {
104+
return set.KeySet[X](bdm.right)
105+
}
106+
107+
// GetLeftKeys returns the keys from the left map.
108+
func (bdm *BidirectionalMap[X, Y]) GetLeftKeys() set.Set[Y] {
109+
return set.KeySet[Y](bdm.left)
110+
}

bidirectionalmap/map_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package bidirectionalmap
18+
19+
import "testing"
20+
21+
func TestMultipleInserts(t *testing.T) {
22+
bidimap := NewBidirectionalMap[string, string]()
23+
bidimap.InsertRight("r1", "l1")
24+
bidimap.InsertRight("r1", "l2")
25+
if bidimap.GetRight("r1").Len() != 2 {
26+
t.Errorf("GetRight('r1').Len() == %d, expected 2", bidimap.GetRight("r1").Len())
27+
}
28+
if bidimap.GetLeft("l2").Len() != 1 {
29+
t.Errorf("GetLeft('l2').Len() == %d, expected 1", bidimap.GetLeft("l2").Len())
30+
}
31+
bidimap.InsertLeft("l2", "r2")
32+
if bidimap.GetLeft("l2").Len() != 2 {
33+
t.Errorf("GetLeft('l2').Len() == %d, expected 2", bidimap.GetLeft("l2").Len())
34+
}
35+
r2Len := bidimap.GetRight("r2").Len()
36+
if r2Len != 1 {
37+
t.Errorf("GetRight('r2').Len() == %d, expected 1", r2Len)
38+
}
39+
bidimap.DeleteRightKey("r2")
40+
if bidimap.GetRight("r2") != nil {
41+
t.Errorf("GetRight('r2') should be nil")
42+
}
43+
if bidimap.GetLeft("l2").Len() != 1 {
44+
t.Errorf("GetLeft('l2').Len() == %d, expected 1", bidimap.GetLeft("l2").Len())
45+
}
46+
}

genericinterfaces/OWNERS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# See the OWNERS docs at https://go.k8s.io/owners
2+
3+
reviewers:
4+
- logicalhan
5+
- thockin
6+
approvers:
7+
- logicalhan
8+
- thockin
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 The Kubernetes Authors.
2+
Copyright 2024 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,40 +14,40 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package set
17+
package genericinterfaces
1818

19-
// ordered is a constraint that permits any ordered type: any type
19+
// Ordered is a constraint that permits any ordered type: any type
2020
// that supports the operators < <= >= >.
2121
// If future releases of Go add new ordered types,
2222
// this constraint will be modified to include them.
23-
type ordered interface {
24-
integer | float | ~string
23+
type Ordered interface {
24+
Integer | Float | ~string
2525
}
2626

27-
// integer is a constraint that permits any integer type.
27+
// Integer is a constraint that permits any integer type.
2828
// If future releases of Go add new predeclared integer types,
2929
// this constraint will be modified to include them.
30-
type integer interface {
31-
signed | unsigned
30+
type Integer interface {
31+
Signed | Unsigned
3232
}
3333

34-
// float is a constraint that permits any floating-point type.
34+
// Float is a constraint that permits any floating-point type.
3535
// If future releases of Go add new predeclared floating-point types,
3636
// this constraint will be modified to include them.
37-
type float interface {
37+
type Float interface {
3838
~float32 | ~float64
3939
}
4040

41-
// signed is a constraint that permits any signed integer type.
41+
// Signed is a constraint that permits any signed integer type.
4242
// If future releases of Go add new predeclared signed integer types,
4343
// this constraint will be modified to include them.
44-
type signed interface {
44+
type Signed interface {
4545
~int | ~int8 | ~int16 | ~int32 | ~int64
4646
}
4747

48-
// unsigned is a constraint that permits any unsigned integer type.
48+
// Unsigned is a constraint that permits any unsigned integer type.
4949
// If future releases of Go add new predeclared unsigned integer types,
5050
// this constraint will be modified to include them.
51-
type unsigned interface {
51+
type Unsigned interface {
5252
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
5353
}

set/set.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,26 @@ package set
1818

1919
import (
2020
"sort"
21+
22+
"k8s.io/utils/genericinterfaces"
2123
)
2224

2325
// Empty is public since it is used by some internal API objects for conversions between external
2426
// string arrays and internal sets, and conversion logic requires public types today.
2527
type Empty struct{}
2628

2729
// Set is a set of the same type elements, implemented via map[ordered]struct{} for minimal memory consumption.
28-
type Set[E ordered] map[E]Empty
30+
type Set[E genericinterfaces.Ordered] map[E]Empty
2931

3032
// New creates a new set.
31-
func New[E ordered](items ...E) Set[E] {
33+
func New[E genericinterfaces.Ordered](items ...E) Set[E] {
3234
ss := Set[E]{}
3335
ss.Insert(items...)
3436
return ss
3537
}
3638

3739
// KeySet creates a Set[E] from a keys of a map[E](? extends interface{}).
38-
func KeySet[E ordered, A any](theMap map[E]A) Set[E] {
40+
func KeySet[E genericinterfaces.Ordered, A any](theMap map[E]A) Set[E] {
3941
ret := Set[E]{}
4042
for key := range theMap {
4143
ret.Insert(key)
@@ -158,7 +160,7 @@ func (s Set[E]) Equal(s2 Set[E]) bool {
158160
return s.Len() == s2.Len() && s.IsSuperset(s2)
159161
}
160162

161-
type sortableSlice[E ordered] []E
163+
type sortableSlice[E genericinterfaces.Ordered] []E
162164

163165
func (s sortableSlice[E]) Len() int {
164166
return len(s)

set/set_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package set
1919
import (
2020
"reflect"
2121
"testing"
22+
23+
"k8s.io/utils/genericinterfaces"
2224
)
2325

2426
func TestStringSetHasAll(t *testing.T) {
@@ -365,7 +367,7 @@ func TestSetClearInSeparateFunction(t *testing.T) {
365367
}
366368
}
367369

368-
func clearSetAndAdd[T ordered](s Set[T], a T) {
370+
func clearSetAndAdd[T genericinterfaces.Ordered](s Set[T], a T) {
369371
s.Clear()
370372
s.Insert(a)
371373
}

0 commit comments

Comments
 (0)