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")
}