From 531fd6dea2101e8d0186f692cd8320c2cf2b17dd Mon Sep 17 00:00:00 2001 From: Han Kang Date: Sat, 2 Mar 2024 16:20:27 -0800 Subject: [PATCH] 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. --- bidirectionalmap/OWNERS | 8 ++ bidirectionalmap/map.go | 107 ++++++++++++++++++++++++++ bidirectionalmap/map_test.go | 46 +++++++++++ genericinterfaces/OWNERS | 8 ++ {set => genericinterfaces}/ordered.go | 28 +++---- set/set.go | 10 ++- set/set_test.go | 4 +- 7 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 bidirectionalmap/OWNERS create mode 100644 bidirectionalmap/map.go create mode 100644 bidirectionalmap/map_test.go create mode 100644 genericinterfaces/OWNERS rename {set => genericinterfaces}/ordered.go (70%) 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) }