From cfb0ceecd53b70073a4dba3bd37d0577f5afd5ab Mon Sep 17 00:00:00 2001 From: kasegao Date: Thu, 27 Oct 2022 13:49:36 +0900 Subject: [PATCH] feat: support generics --- examples/examples_generics.go | 76 +++++++++++++++++++ generics_test.go | 68 +++++++++++++++++ key_pair_generics.go | 15 ++++ node_generics.go | 26 +++++++ ordered_map_generics.go | 117 +++++++++++++++++++++++++++++ ordered_map_generics_test.go | 136 ++++++++++++++++++++++++++++++++++ 6 files changed, 438 insertions(+) create mode 100644 examples/examples_generics.go create mode 100644 generics_test.go create mode 100644 key_pair_generics.go create mode 100644 node_generics.go create mode 100644 ordered_map_generics.go create mode 100644 ordered_map_generics_test.go diff --git a/examples/examples_generics.go b/examples/examples_generics.go new file mode 100644 index 0000000..035a527 --- /dev/null +++ b/examples/examples_generics.go @@ -0,0 +1,76 @@ +//go:build go1.18 +// +build go1.18 + +package main + +import ( + "fmt" + + "github.com/cevaris/ordered_map" +) + +func GetAndSetExampleG() { + // Init new OrderedMap + om := ordered_map.NewOrderedMapG[string, int]() + + // Set key + om.Set("a", 1) + om.Set("b", 2) + om.Set("c", 3) + om.Set("d", 4) + + // Same interface as builtin map + if val, ok := om.Get("b"); ok { + // Found key "b" + fmt.Println(val) + } + + // Delete a key + om.Delete("c") + + // Failed Get lookup becase we deleted "c" + if _, ok := om.Get("c"); !ok { + // Did not find key "c" + fmt.Println("c not found") + } +} + +func IteratorExampleG() { + n := 100 + om := ordered_map.NewOrderedMapG[int, string]() + + for i := 0; i < n; i++ { + // Insert data into OrderedMap + om.Set(i, fmt.Sprintf("%d", i*i)) + } + + // Iterate though values + // - Values iteration are in insert order + // - Returned in a key/value pair struct + iter := om.IterFunc() + for kv, ok := iter(); ok; kv, ok = iter() { + fmt.Println(kv, kv.Key, kv.Value) + } +} + +type MyStructG struct { + a int + b float64 +} + +func CustomStructG() { + om := ordered_map.NewOrderedMapG[string, *MyStructG]() + om.Set("one", &MyStructG{1, 1.1}) + om.Set("two", &MyStructG{2, 2.2}) + om.Set("three", &MyStructG{3, 3.3}) + + fmt.Println(om) + // Ouput: OrderedMap[one:&{1 1.1}, two:&{2 2.2}, three:&{3 3.3}] + +} + +// func main() { +// GetAndSetExampleG() +// IteratorExampleG() +// CustomStructG() +// } diff --git a/generics_test.go b/generics_test.go new file mode 100644 index 0000000..fb7149a --- /dev/null +++ b/generics_test.go @@ -0,0 +1,68 @@ +//go:build go1.18 +// +build go1.18 + +package ordered_map + +import ( + "testing" +) + +func BenchmarkSet_OrderedMap(b *testing.B) { + om := NewOrderedMap() + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } +} + +func BenchmarkSet_OrderedMapG(b *testing.B) { + om := NewOrderedMapG[int, int]() + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } +} + +func BenchmarkGet_OrderedMap(b *testing.B) { + om := NewOrderedMap() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Get(i) + } +} + +func BenchmarkGet_OrderedMapG(b *testing.B) { + om := NewOrderedMapG[int, int]() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Get(i) + } +} + +func BenchmarkDelete_OrderedMap(b *testing.B) { + om := NewOrderedMap() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Delete(i) + } +} + +func BenchmarkDelete_OrderedMapG(b *testing.B) { + om := NewOrderedMapG[int, int]() + for i := 0; i < b.N; i++ { + om.Set(i, i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + om.Delete(i) + } +} diff --git a/key_pair_generics.go b/key_pair_generics.go new file mode 100644 index 0000000..0f012e7 --- /dev/null +++ b/key_pair_generics.go @@ -0,0 +1,15 @@ +//go:build go1.18 +// +build go1.18 + +package ordered_map + +import "fmt" + +type KVPairG[K comparable, V any] struct { + Key K + Value V +} + +func (k *KVPairG[K, V]) String() string { + return fmt.Sprintf("%v:%v", k.Key, k.Value) +} diff --git a/node_generics.go b/node_generics.go new file mode 100644 index 0000000..6e42283 --- /dev/null +++ b/node_generics.go @@ -0,0 +1,26 @@ +//go:build go1.18 +// +build go1.18 + +package ordered_map + +type nodeG[K comparable, V any] struct { + prev *nodeG[K, V] + next *nodeG[K, V] + key K + value V +} + +func newRootNodeG[K comparable, V any]() *nodeG[K, V] { + root := &nodeG[K, V]{} + root.prev = root + root.next = root + return root +} + +func newNodeG[K comparable, V any](prev *nodeG[K, V], next *nodeG[K, V], key K, value V) *nodeG[K, V] { + return &nodeG[K, V]{prev: prev, next: next, key: key, value: value} +} + +func (n *nodeG[K, V]) kvpair() *KVPairG[K, V] { + return &KVPairG[K, V]{Key: n.key, Value: n.value} +} diff --git a/ordered_map_generics.go b/ordered_map_generics.go new file mode 100644 index 0000000..1ded988 --- /dev/null +++ b/ordered_map_generics.go @@ -0,0 +1,117 @@ +//go:build go1.18 +// +build go1.18 + +package ordered_map + +import ( + "fmt" +) + +type OrderedMapG[K comparable, V any] struct { + mapper map[K]*nodeG[K, V] + root *nodeG[K, V] +} + +func NewOrderedMapG[K comparable, V any]() *OrderedMapG[K, V] { + om := &OrderedMapG[K, V]{ + mapper: make(map[K]*nodeG[K, V]), + root: newRootNodeG[K, V](), + } + return om +} + +func NewOrderedMapGWithArgs[K comparable, V any](args []*KVPairG[K, V]) *OrderedMapG[K, V] { + om := NewOrderedMapG[K, V]() + om.update(args) + return om +} + +func (om *OrderedMapG[K, V]) update(args []*KVPairG[K, V]) { + for _, pair := range args { + om.Set(pair.Key, pair.Value) + } +} + +func (om *OrderedMapG[K, V]) Set(key K, value V) { + if n, ok := om.mapper[key]; ok { + n.value = value + } + + root := om.root + last := root.prev + n := newNodeG(last, root, key, value) + last.next = n + root.prev = n + om.mapper[key] = n +} + +func (om *OrderedMapG[K, V]) Get(key K) (value V, ok bool) { + if n, ok := om.mapper[key]; ok { + return n.value, true + } + return +} + +func (om *OrderedMapG[K, V]) Delete(key K) { + n, ok := om.mapper[key] + if ok { + n.prev.next = n.next + n.next.prev = n.prev + delete(om.mapper, key) + } +} + +func (om *OrderedMapG[K, V]) String() string { + builder := make([]string, len(om.mapper)) + + var index int = 0 + iter := om.IterFunc() + for kv, ok := iter(); ok; kv, ok = iter() { + val, _ := om.Get(kv.Key) + builder[index] = fmt.Sprintf("%v:%v", kv.Key, val) + index++ + } + return fmt.Sprintf("OrderedMap%v", builder) +} + +func (om *OrderedMapG[K, V]) Iter() <-chan *KVPairG[K, V] { + println("Iter() method is deprecated!. Use IterFunc() instead.") + return om.UnsafeIter() +} + +/* +Beware, Iterator leaks goroutines if we do not fully traverse the map. +For most cases, `IterFunc()` should work as an iterator. +*/ +func (om *OrderedMapG[K, V]) UnsafeIter() <-chan *KVPairG[K, V] { + keys := make(chan *KVPairG[K, V]) + go func() { + defer close(keys) + var curr *nodeG[K, V] + root := om.root + curr = root.next + for curr != root { + keys <- curr.kvpair() + curr = curr.next + } + }() + return keys +} + +func (om *OrderedMapG[K, V]) IterFunc() func() (*KVPairG[K, V], bool) { + var curr *nodeG[K, V] + root := om.root + curr = root.next + return func() (kvpair *KVPairG[K, V], ok bool) { + for curr != root { + kvpair = curr.kvpair() + curr = curr.next + return kvpair, true + } + return + } +} + +func (om *OrderedMapG[K, V]) Len() int { + return len(om.mapper) +} diff --git a/ordered_map_generics_test.go b/ordered_map_generics_test.go new file mode 100644 index 0000000..a850d32 --- /dev/null +++ b/ordered_map_generics_test.go @@ -0,0 +1,136 @@ +//go:build go1.18 +// +build go1.18 + +package ordered_map + +import ( + "testing" +) + +func testStringIntG() []*KVPairG[string, int] { + var data []*KVPairG[string, int] = make([]*KVPairG[string, int], 5) + data[0] = &KVPairG[string, int]{"test0", 0} + data[1] = &KVPairG[string, int]{"test1", 1} + data[2] = &KVPairG[string, int]{"test2", 2} + data[3] = &KVPairG[string, int]{"test3", 3} + data[4] = &KVPairG[string, int]{"test4", 4} + return data +} + +func TestSetDataG(t *testing.T) { + expected := testStringIntG() + om := NewOrderedMapG[string, int]() + if om == nil { + t.Error("Failed to create OrderedMap") + } + + for _, kvp := range expected { + om.Set(kvp.Key, kvp.Value) + } + + if om.Len() != len(expected) { + t.Error("Failed insert of args:", om.mapper, expected) + } +} + +func TestGetDataG(t *testing.T) { + data := testStringIntG() + om := NewOrderedMapGWithArgs(data) + + for _, kvp := range data { + val, ok := om.Get(kvp.Key) + if ok && kvp.Value != val { + t.Error(kvp.Value, val) + } + } + _, ok := om.Get("invlalid-key") + if ok { + t.Error("Invalid key was found in OrderedMap") + } +} + +func TestDeleteDataG(t *testing.T) { + data := testStringIntG() + om := NewOrderedMapGWithArgs(data) + + testKey := data[2].Key + + // First check to see if exists + _, ok := om.Get(testKey) + if !ok { + t.Error("Key/Value not found in OrderedMap") + } + + // Assert size equal to "test data size" + if om.Len() != len(data) { + t.Error("mapper size is incorrect") + } + + // Delete key + om.Delete(testKey) + + // Assert size equal to "test data size" - 1 + if om.Len() != (len(data) - 1) { + t.Error("mapper size is incorrect") + } + + // Test to see if removed + _, ok2 := om.Get(testKey) + if ok2 { + t.Error("Key/Value was not deleted") + } +} + +func TestIteratorG(t *testing.T) { + sample := testStringIntG() + om := NewOrderedMapGWithArgs(sample) + iter := om.UnsafeIter() + if iter == nil { + t.Error("Failed to create OrderedMap") + } + + var index int = 0 + for k := range iter { + expected := sample[index] + if k.Key != expected.Key || k.Value != expected.Value { + t.Error(expected, k) + } + index++ + } +} + +func TestIteratorFuncG(t *testing.T) { + sample := testStringIntG() + om := NewOrderedMapGWithArgs(sample) + + iter := om.IterFunc() + if iter == nil { + t.Error("Failed to create OrderedMap") + } + + var index int = 0 + for k, ok := iter(); ok; k, ok = iter() { + expected := sample[index] + if k.Key != expected.Key || k.Value != expected.Value { + t.Error(expected, k) + } + index++ + } +} + +func TestLenNonEmptyG(t *testing.T) { + data := testStringIntG() + om := NewOrderedMapGWithArgs(data) + + if om.Len() != len(data) { + t.Fatal("Unexpected length") + } +} + +func TestLenEmptyG(t *testing.T) { + om := NewOrderedMapG[string, int]() + + if om.Len() != 0 { + t.Fatal("Unexpected length") + } +}