mirror of
https://github.com/jpeletier/koolnova2mqtt.git
synced 2026-07-16 04:16:56 +00:00
added bimap
This commit is contained in:
+143
@@ -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()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user