diff --git a/bidirectionalmap/OWNERS b/bidirectionalmap/OWNERS new file mode 100644 index 00000000..9d2d33e7 --- /dev/null +++ b/bidirectionalmap/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - logicalhan + - thockin +approvers: + - logicalhan + - thockin diff --git a/bidirectionalmap/map.go b/bidirectionalmap/map.go new file mode 100644 index 00000000..e8857806 --- /dev/null +++ b/bidirectionalmap/map.go @@ -0,0 +1,107 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bidirectionalmap + +import ( + "k8s.io/utils/genericinterfaces" + "k8s.io/utils/set" +) + +// BidirectionalMap is a bidirectional map. +type BidirectionalMap[X genericinterfaces.Ordered, Y genericinterfaces.Ordered] struct { + right map[X]set.Set[Y] + left map[Y]set.Set[X] +} + +// NewBidirectionalMap creates a new BidirectionalMap. +func NewBidirectionalMap[X genericinterfaces.Ordered, Y genericinterfaces.Ordered]() *BidirectionalMap[X, Y] { + return &BidirectionalMap[X, Y]{ + right: make(map[X]set.Set[Y]), + left: make(map[Y]set.Set[X]), + } +} + +// InsertRight inserts a new item into the right map, return true if the key-value was not already +// present in the map, false otherwise +func (bdm *BidirectionalMap[X, Y]) InsertRight(x X, y Y) bool { + if bdm.right[x] == nil { + bdm.right[x] = set.New[Y]() + } + if bdm.right[x].Has(y) { + return false + } + if bdm.left[y] == nil { + bdm.left[y] = set.New[X]() + } + bdm.right[x].Insert(y) + bdm.left[y].Insert(x) + return true +} + +// InsertLeft inserts a new item into the left map, return true if the key-value was not already +// present in the map, false otherwise +func (bdm *BidirectionalMap[X, Y]) InsertLeft(y Y, x X) bool { + return bdm.InsertRight(x, y) +} + +// GetRight returns a value from the right map. +func (bdm *BidirectionalMap[X, Y]) GetRight(x X) set.Set[Y] { + return bdm.right[x] +} + +// GetLeft returns a value from left map. +func (bdm *BidirectionalMap[X, Y]) GetLeft(y Y) set.Set[X] { + return bdm.left[y] +} + +// DeleteRightKey deletes the key from the right map and removes +// the inverse mapping from the left map. +func (bdm *BidirectionalMap[X, Y]) DeleteRightKey(x X) { + if leftValues, ok := bdm.right[x]; ok { + delete(bdm.right, x) + for y := range leftValues { + bdm.left[y].Delete(x) + if bdm.left[y].Len() == 0 { + delete(bdm.left, y) + } + } + } +} + +// DeleteLeftKey deletes the key from the left map and removes +// the inverse mapping from the right map. +func (bdm *BidirectionalMap[X, Y]) DeleteLeftKey(y Y) { + if rightValues, ok := bdm.left[y]; ok { + delete(bdm.left, y) + for x := range rightValues { + bdm.right[x].Delete(y) + if bdm.right[x].Len() == 0 { + delete(bdm.right, x) + } + } + } +} + +// GetRightKeys returns the keys from the right map. +func (bdm *BidirectionalMap[X, Y]) GetRightKeys() set.Set[X] { + return set.KeySet[X](bdm.right) +} + +// GetLeftKeys returns the keys from the left map. +func (bdm *BidirectionalMap[X, Y]) GetLeftKeys() set.Set[Y] { + return set.KeySet[Y](bdm.left) +} diff --git a/bidirectionalmap/map_test.go b/bidirectionalmap/map_test.go new file mode 100644 index 00000000..368073d1 --- /dev/null +++ b/bidirectionalmap/map_test.go @@ -0,0 +1,46 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bidirectionalmap + +import "testing" + +func TestMultipleInserts(t *testing.T) { + bidimap := NewBidirectionalMap[string, string]() + bidimap.InsertRight("r1", "l1") + bidimap.InsertRight("r1", "l2") + if bidimap.GetRight("r1").Len() != 2 { + t.Errorf("GetRight('r1').Len() == %d, expected 2", bidimap.GetRight("r1").Len()) + } + if bidimap.GetLeft("l2").Len() != 1 { + t.Errorf("GetLeft('l2').Len() == %d, expected 1", bidimap.GetLeft("l2").Len()) + } + bidimap.InsertLeft("l2", "r2") + if bidimap.GetLeft("l2").Len() != 2 { + t.Errorf("GetLeft('l2').Len() == %d, expected 2", bidimap.GetLeft("l2").Len()) + } + r2Len := bidimap.GetRight("r2").Len() + if r2Len != 1 { + t.Errorf("GetRight('r2').Len() == %d, expected 1", r2Len) + } + bidimap.DeleteRightKey("r2") + if bidimap.GetRight("r2") != nil { + t.Errorf("GetRight('r2') should be nil") + } + if bidimap.GetLeft("l2").Len() != 1 { + t.Errorf("GetLeft('l2').Len() == %d, expected 1", bidimap.GetLeft("l2").Len()) + } +} diff --git a/genericinterfaces/OWNERS b/genericinterfaces/OWNERS new file mode 100644 index 00000000..9d2d33e7 --- /dev/null +++ b/genericinterfaces/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - logicalhan + - thockin +approvers: + - logicalhan + - thockin diff --git a/set/ordered.go b/genericinterfaces/ordered.go similarity index 70% rename from set/ordered.go rename to genericinterfaces/ordered.go index 2b2c11fc..e914681f 100644 --- a/set/ordered.go +++ b/genericinterfaces/ordered.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The Kubernetes Authors. +Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); 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 limitations under the License. */ -package set +package genericinterfaces -// ordered is a constraint that permits any ordered type: any type +// Ordered is a constraint that permits any ordered type: any type // that supports the operators < <= >= >. // If future releases of Go add new ordered types, // this constraint will be modified to include them. -type ordered interface { - integer | float | ~string +type Ordered interface { + Integer | Float | ~string } -// integer is a constraint that permits any integer type. +// Integer is a constraint that permits any integer type. // If future releases of Go add new predeclared integer types, // this constraint will be modified to include them. -type integer interface { - signed | unsigned +type Integer interface { + Signed | Unsigned } -// float is a constraint that permits any floating-point type. +// Float is a constraint that permits any floating-point type. // If future releases of Go add new predeclared floating-point types, // this constraint will be modified to include them. -type float interface { +type Float interface { ~float32 | ~float64 } -// signed is a constraint that permits any signed integer type. +// Signed is a constraint that permits any signed integer type. // If future releases of Go add new predeclared signed integer types, // this constraint will be modified to include them. -type signed interface { +type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } -// unsigned is a constraint that permits any unsigned integer type. +// Unsigned is a constraint that permits any unsigned integer type. // If future releases of Go add new predeclared unsigned integer types, // this constraint will be modified to include them. -type unsigned interface { +type Unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } diff --git a/set/set.go b/set/set.go index fc25b2fe..fb029373 100644 --- a/set/set.go +++ b/set/set.go @@ -18,6 +18,8 @@ package set import ( "sort" + + "k8s.io/utils/genericinterfaces" ) // Empty is public since it is used by some internal API objects for conversions between external @@ -25,17 +27,17 @@ import ( type Empty struct{} // Set is a set of the same type elements, implemented via map[ordered]struct{} for minimal memory consumption. -type Set[E ordered] map[E]Empty +type Set[E genericinterfaces.Ordered] map[E]Empty // New creates a new set. -func New[E ordered](items ...E) Set[E] { +func New[E genericinterfaces.Ordered](items ...E) Set[E] { ss := Set[E]{} ss.Insert(items...) return ss } // KeySet creates a Set[E] from a keys of a map[E](? extends interface{}). -func KeySet[E ordered, A any](theMap map[E]A) Set[E] { +func KeySet[E genericinterfaces.Ordered, A any](theMap map[E]A) Set[E] { ret := Set[E]{} for key := range theMap { ret.Insert(key) @@ -158,7 +160,7 @@ func (s Set[E]) Equal(s2 Set[E]) bool { return s.Len() == s2.Len() && s.IsSuperset(s2) } -type sortableSlice[E ordered] []E +type sortableSlice[E genericinterfaces.Ordered] []E func (s sortableSlice[E]) Len() int { return len(s) diff --git a/set/set_test.go b/set/set_test.go index a4bef5c0..7e433054 100644 --- a/set/set_test.go +++ b/set/set_test.go @@ -19,6 +19,8 @@ package set import ( "reflect" "testing" + + "k8s.io/utils/genericinterfaces" ) func TestStringSetHasAll(t *testing.T) { @@ -365,7 +367,7 @@ func TestSetClearInSeparateFunction(t *testing.T) { } } -func clearSetAndAdd[T ordered](s Set[T], a T) { +func clearSetAndAdd[T genericinterfaces.Ordered](s Set[T], a T) { s.Clear() s.Insert(a) }