added bimap

This commit is contained in:
Javier Peletier
2020-12-12 12:15:19 +01:00
parent 4662308fd0
commit 39e383812e
4 changed files with 462 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
package bimap
// Package bimap provides a threadsafe bidirectional map
import "sync"
// BiMap is a bi-directional hashmap that is thread safe and supports immutability
type BiMap struct {
s sync.RWMutex
immutable bool
forward map[interface{}]interface{}
inverse map[interface{}]interface{}
}
// NewBiMap returns a an empty, mutable, biMap
func NewBiMap() *BiMap {
return &BiMap{forward: make(map[interface{}]interface{}), inverse: make(map[interface{}]interface{}), immutable: false}
}
// Insert puts a key and value into the BiMap, provided its mutable. Also creates the reverse mapping from value to key.
func (b *BiMap) Insert(k interface{}, v interface{}) {
b.s.RLock()
if b.immutable {
panic("Cannot modify immutable map")
}
b.s.RUnlock()
b.s.Lock()
defer b.s.Unlock()
b.forward[k] = v
b.inverse[v] = k
}
// Exists checks whether or not a key exists in the BiMap
func (b *BiMap) Exists(k interface{}) bool {
b.s.RLock()
defer b.s.RUnlock()
_, ok := b.forward[k]
return ok
}
// ExistsInverse checks whether or not a value exists in the BiMap
func (b *BiMap) ExistsInverse(k interface{}) bool {
b.s.RLock()
defer b.s.RUnlock()
_, ok := b.inverse[k]
return ok
}
// Get returns the value for a given key in the BiMap and whether or not the element was present.
func (b *BiMap) Get(k interface{}) (interface{}, bool) {
if !b.Exists(k) {
return "", false
}
b.s.RLock()
defer b.s.RUnlock()
return b.forward[k], true
}
// GetInverse returns the key for a given value in the BiMap and whether or not the element was present.
func (b *BiMap) GetInverse(v interface{}) (interface{}, bool) {
if !b.ExistsInverse(v) {
return "", false
}
b.s.RLock()
defer b.s.RUnlock()
return b.inverse[v], true
}
// Delete removes a key-value pair from the BiMap for a given key. Returns if the key doesn't exist
func (b *BiMap) Delete(k interface{}) {
b.s.RLock()
if b.immutable {
panic("Cannot modify immutable map")
}
b.s.RUnlock()
if !b.Exists(k) {
return
}
val, _ := b.Get(k)
b.s.Lock()
defer b.s.Unlock()
delete(b.forward, k)
delete(b.inverse, val)
}
// DeleteInverse emoves a key-value pair from the BiMap for a given value. Returns if the value doesn't exist
func (b *BiMap) DeleteInverse(v interface{}) {
b.s.RLock()
if b.immutable {
panic("Cannot modify immutable map")
}
b.s.RUnlock()
if !b.ExistsInverse(v) {
return
}
key, _ := b.GetInverse(v)
b.s.Lock()
defer b.s.Unlock()
delete(b.inverse, v)
delete(b.forward, key)
}
// Size returns the number of elements in the bimap
func (b *BiMap) Size() int {
b.s.RLock()
defer b.s.RUnlock()
return len(b.forward)
}
// MakeImmutable freezes the BiMap preventing any further write actions from taking place
func (b *BiMap) MakeImmutable() {
b.s.Lock()
defer b.s.Unlock()
b.immutable = true
}
// GetInverseMap returns a regular go map mapping from the BiMap's values to its keys
func (b *BiMap) GetInverseMap() map[interface{}]interface{} {
return b.inverse
}
// GetForwardMap returns a regular go map mapping from the BiMap's keys to its values
func (b *BiMap) GetForwardMap() map[interface{}]interface{} {
return b.forward
}
// Lock manually locks the BiMap's mutex
func (b *BiMap) Lock() {
b.s.Lock()
}
// Unlock manually unlocks the BiMap's mutex
func (b *BiMap) Unlock() {
b.s.Unlock()
}
+308
View File
@@ -0,0 +1,308 @@
package bimap
import (
"reflect"
"runtime/debug"
"testing"
"github.com/epiclabs-io/ut"
)
const key = "key"
const value = "value"
// isEmpty gets whether the specified object is considered empty or not.
func isEmpty(object interface{}) bool {
// get nil case out of the way
if object == nil {
return true
}
objValue := reflect.ValueOf(object)
switch objValue.Kind() {
// collection types are empty when they have no element
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
return objValue.Len() == 0
// pointers are empty if nil or if the value they point to is empty
case reflect.Ptr:
if objValue.IsNil() {
return true
}
deref := objValue.Elem().Interface()
return isEmpty(deref)
// for all other types, compare against the zero value
default:
zero := reflect.Zero(objValue.Type())
return reflect.DeepEqual(object, zero.Interface())
}
}
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
func didPanic(f func()) (bool, interface{}, string) {
didPanic := false
var message interface{}
var stack string
func() {
defer func() {
if message = recover(); message != nil {
didPanic = true
stack = string(debug.Stack())
}
}()
// call the target function
f()
}()
return didPanic, message, stack
}
func TestNewBiMap(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
expected := &BiMap{forward: make(map[interface{}]interface{}), inverse: make(map[interface{}]interface{})}
t.Equals(expected, actual)
}
func TestBiMap_Insert(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
actual.Insert(key, value)
fwdExpected := make(map[interface{}]interface{})
invExpected := make(map[interface{}]interface{})
fwdExpected[key] = value
invExpected[value] = key
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
t.Equals(expected, actual)
}
func TestBiMap_Exists(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
actual.Insert(key, value)
t.Assert(!actual.Exists("ARBITARY_KEY"), "Key should not exist")
t.Assert(actual.Exists(key), "Inserted key should exist")
}
func TestBiMap_InverseExists(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
actual.Insert(key, value)
t.Assert(!actual.ExistsInverse("ARBITARY_VALUE"), "Value should not exist")
t.Assert(actual.ExistsInverse(value), "Inserted value should exist")
}
func TestBiMap_Get(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
actual.Insert(key, value)
actualVal, ok := actual.Get(key)
t.Assert(ok, "It should return true")
t.Equals(value, actualVal)
actualVal, ok = actual.Get(value)
t.Assert(!ok, "It should return false")
t.Assert(isEmpty(actualVal), "Actual val should be empty")
}
func TestBiMap_GetInverse(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
actual.Insert(key, value)
actualKey, ok := actual.GetInverse(value)
t.Assert(ok, "It should return true")
t.Equals(key, actualKey)
actualKey, ok = actual.Get(value)
t.Assert(!ok, "It should return false")
t.Assert(isEmpty(actualKey), "Actual key should be empty")
}
func TestBiMap_Size(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
t.Equals(0, actual.Size())
actual.Insert(key, value)
t.Equals(1, actual.Size())
}
func TestBiMap_Delete(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "DummyKey"
dummyVal := "DummyVal"
actual.Insert(key, value)
actual.Insert(dummyKey, dummyVal)
t.Equals(2, actual.Size())
actual.Delete(dummyKey)
fwdExpected := make(map[interface{}]interface{})
invExpected := make(map[interface{}]interface{})
fwdExpected[key] = value
invExpected[value] = key
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
t.Equals(1, actual.Size())
t.Equals(expected, actual)
actual.Delete(dummyKey)
t.Equals(1, actual.Size())
t.Equals(expected, actual)
}
func TestBiMap_InverseDelete(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "DummyKey"
dummyVal := "DummyVal"
actual.Insert(key, value)
actual.Insert(dummyKey, dummyVal)
t.Equals(2, actual.Size())
actual.DeleteInverse(dummyVal)
fwdExpected := make(map[interface{}]interface{})
invExpected := make(map[interface{}]interface{})
fwdExpected[key] = value
invExpected[value] = key
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
t.Equals(1, actual.Size())
t.Equals(expected, actual)
actual.DeleteInverse(dummyVal)
t.Equals(1, actual.Size())
t.Equals(expected, actual)
}
func TestBiMap_WithVaryingType(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "Dummy key"
dummyVal := 3
actual.Insert(dummyKey, dummyVal)
res, _ := actual.Get(dummyKey)
resVal, _ := actual.GetInverse(dummyVal)
t.Equals(dummyVal, res)
t.Equals(dummyKey, resVal)
}
func TestBiMap_MakeImmutable(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "Dummy key"
dummyVal := 3
actual.Insert(dummyKey, dummyVal)
actual.MakeImmutable()
panicked, _, _ := didPanic(func() {
actual.Delete(dummyKey)
})
t.Assert(panicked, "It should panic on a mutation operation")
val, _ := actual.Get(dummyKey)
t.Equals(dummyVal, val)
panicked, _, _ = didPanic(func() {
actual.DeleteInverse(dummyVal)
})
t.Assert(panicked, "It should panic on a mutation operation")
key, _ := actual.GetInverse(dummyVal)
t.Equals(dummyKey, key)
size := actual.Size()
t.Equals(1, size)
panicked, _, _ = didPanic(func() {
actual.Insert("New", 1)
})
t.Assert(panicked, "It should panic on a mutation operation")
size = actual.Size()
t.Equals(1, size)
}
func TestBiMap_GetForwardMap(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "Dummy key"
dummyVal := 42
forwardMap := make(map[interface{}]interface{})
forwardMap[dummyKey] = dummyVal
actual.Insert(dummyKey, dummyVal)
actualForwardMap := actual.GetForwardMap()
eq := reflect.DeepEqual(actualForwardMap, forwardMap)
t.Assert(eq, "Forward maps should be equal")
}
func TestBiMap_GetInverseMap(tx *testing.T) {
t := ut.BeginTest(tx, false)
defer t.FinishTest()
actual := NewBiMap()
dummyKey := "Dummy key"
dummyVal := 42
inverseMap := make(map[interface{}]interface{})
inverseMap[dummyVal] = dummyKey
actual.Insert(dummyKey, dummyVal)
actualInverseMap := actual.GetInverseMap()
eq := reflect.DeepEqual(actualInverseMap, inverseMap)
t.Assert(eq, "Inverse maps should be equal")
}
+1
View File
@@ -8,4 +8,5 @@ require (
github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947
github.com/goburrow/modbus v0.1.0
github.com/goburrow/serial v0.1.0 // indirect
github.com/stretchr/testify v1.6.1
)
+10
View File
@@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.3.0 h1:MU79lqr3FKNKbSrGN7d7bNYqh8MwWW7Zcx0iG+VIw9I=
github.com/eclipse/paho.mqtt.golang v1.3.0/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609 h1:KHcpmcC/8cnCDXDm6SaCTajWF/vyUbBE1ovA27xYYEY=
@@ -10,9 +12,17 @@ github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=