From 2948dd317d5df6865e2181bcdee35510b23753df Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Tue, 13 Aug 2024 20:40:49 +0100 Subject: [PATCH 1/3] feat: add support for iterators introduced in go1.23 --- go.mod | 2 +- orderedmap.go | 35 ++++++++++++++++++++++++++++++++++- orderedmap_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ea605d4..6d93aad 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/wk8/go-ordered-map/v2 -go 1.18 +go 1.23 require ( github.com/bahlo/generic-list-go v0.2.0 diff --git a/orderedmap.go b/orderedmap.go index 0647141..c189eb3 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -4,11 +4,11 @@ // All operations are constant-time. // // Github repo: https://github.com/wk8/go-ordered-map -// package orderedmap import ( "fmt" + "iter" list "github.com/bahlo/generic-list-go" ) @@ -294,3 +294,36 @@ func (om *OrderedMap[K, V]) GetAndMoveToFront(key K) (val V, err error) { return } + +// All returns an iterator over all the key-value pairs in the map. +func (om *OrderedMap[K, V]) All() iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Key, pair.Value) { + return + } + } + } +} + +// Keys returns an iterator over all the keys in the map. +func (om *OrderedMap[K, V]) Keys() iter.Seq[K] { + return func(yield func(K) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Key) { + return + } + } + } +} + +// Values returns an iterator over all the values in the map. +func (om *OrderedMap[K, V]) Values() iter.Seq[V] { + return func(yield func(V) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Value) { + return + } + } + } +} diff --git a/orderedmap_test.go b/orderedmap_test.go index a3137eb..e33ac22 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -382,3 +382,45 @@ func TestNilMap(t *testing.T) { assert.Nil(t, om.Newest()) }) } + +func TestIterators(t *testing.T) { + om := New[int, any]() + om.Set(1, "bar") + om.Set(2, 28) + om.Set(3, 100) + om.Set(4, "baz") + om.Set(5, "28") + om.Set(6, "100") + om.Set(7, "baz") + om.Set(8, "baz") + + expectedKeys := []int{1, 2, 3, 4, 5, 6, 7, 8} + expectedValues := []any{"bar", 28, 100, "baz", "28", "100", "baz", "baz"} + + var keys []int + var values []any + + for k, v := range om.All() { + keys = append(keys, k) + values = append(values, v) + } + + assert.Equal(t, expectedKeys, keys) + assert.Equal(t, expectedValues, values) + + keys = []int{} + + for k := range om.Keys() { + keys = append(keys, k) + } + + assert.Equal(t, expectedKeys, keys) + + values = []any{} + + for v := range om.Values() { + values = append(values, v) + } + + assert.Equal(t, expectedValues, values) +} From 86d757218ca6ca407299173205d27741221fe50d Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Tue, 13 Aug 2024 21:18:08 +0100 Subject: [PATCH 2/3] chore: update readme and rename methods plus adding alternatives for newest --- README.md | 35 +++++++++++++++++++++++++++++++++- orderedmap.go | 47 +++++++++++++++++++++++++++++++++++++++------- orderedmap_test.go | 34 ++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b028944..c9ca813 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ Or use your favorite golang vendoring tool! ## Supported go versions -Go >= 1.18 is required to use version >= 2 of this library, as it uses generics. +Go >= 1.23 is required to use version >= 2.2.0 of this library, as it uses generics and iterators. + +if you're running go < 1.23, you can use [version 2.1.8](https://github.com/wk8/go-ordered-map/tree/v2.1.8) instead. If you're running go < 1.18, you can use [version 1](https://github.com/wk8/go-ordered-map/tree/v1) instead. @@ -145,6 +147,37 @@ err := yaml.Unmarshal(data, &om) ... ``` +## Iterator support (go >= 1.23) + +The `FromOldest`, `FromNewest`, `KeysFromOldest`, `KeysFromNewest`, `ValuesFromOldest` and `ValuesFromNewest` methods return iterators over the map's pairs, starting from the oldest or newest pair, respectively. + +For example: + +```go +om := orderedmap.New[int, string]() +om.Set(1, "foo") +om.Set(2, "bar") +om.Set(3, "baz") + +for k, v := range om.FromOldest() { + fmt.Printf("%d => %s\n", k, v) +} + +// prints: +// 1 => foo +// 2 => bar +// 3 => baz + +for k := range om.KeysNewest() { + fmt.Printf("%d\n", k) +} + +// prints: +// 3 +// 2 +// 1 +``` + ## Alternatives There are several other ordered map golang implementations out there, but I believe that at the time of writing none of them offer the same functionality as this library; more specifically: diff --git a/orderedmap.go b/orderedmap.go index c189eb3..a7f9500 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -180,7 +180,7 @@ func (om *OrderedMap[K, V]) Oldest() *Pair[K, V] { // Newest returns a pointer to the newest pair. It's meant to be used to iterate on the ordered map's // pairs from the newest to the oldest, e.g.: -// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } +// for pair := orderedMap.Newest(); pair != nil; pair = pair.Prev() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } func (om *OrderedMap[K, V]) Newest() *Pair[K, V] { if om == nil || om.list == nil { return nil @@ -295,8 +295,8 @@ func (om *OrderedMap[K, V]) GetAndMoveToFront(key K) (val V, err error) { return } -// All returns an iterator over all the key-value pairs in the map. -func (om *OrderedMap[K, V]) All() iter.Seq2[K, V] { +// FromOldest returns an iterator over all the key-value pairs in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) FromOldest() iter.Seq2[K, V] { return func(yield func(K, V) bool) { for pair := om.Oldest(); pair != nil; pair = pair.Next() { if !yield(pair.Key, pair.Value) { @@ -306,8 +306,19 @@ func (om *OrderedMap[K, V]) All() iter.Seq2[K, V] { } } -// Keys returns an iterator over all the keys in the map. -func (om *OrderedMap[K, V]) Keys() iter.Seq[K] { +// FromNewest returns an iterator over all the key-value pairs in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) FromNewest() iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Key, pair.Value) { + return + } + } + } +} + +// KeysFromOldest returns an iterator over all the keys in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) KeysFromOldest() iter.Seq[K] { return func(yield func(K) bool) { for pair := om.Oldest(); pair != nil; pair = pair.Next() { if !yield(pair.Key) { @@ -317,8 +328,19 @@ func (om *OrderedMap[K, V]) Keys() iter.Seq[K] { } } -// Values returns an iterator over all the values in the map. -func (om *OrderedMap[K, V]) Values() iter.Seq[V] { +// KeysFromNewest returns an iterator over all the keys in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) KeysFromNewest() iter.Seq[K] { + return func(yield func(K) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Key) { + return + } + } + } +} + +// ValuesFromOldest returns an iterator over all the values in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) ValuesFromOldest() iter.Seq[V] { return func(yield func(V) bool) { for pair := om.Oldest(); pair != nil; pair = pair.Next() { if !yield(pair.Value) { @@ -327,3 +349,14 @@ func (om *OrderedMap[K, V]) Values() iter.Seq[V] { } } } + +// ValuesFromNewest returns an iterator over all the values in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) ValuesFromNewest() iter.Seq[V] { + return func(yield func(V) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Value) { + return + } + } + } +} diff --git a/orderedmap_test.go b/orderedmap_test.go index e33ac22..b9d6754 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -395,12 +395,14 @@ func TestIterators(t *testing.T) { om.Set(8, "baz") expectedKeys := []int{1, 2, 3, 4, 5, 6, 7, 8} + expectedKeysFromNewest := []int{8, 7, 6, 5, 4, 3, 2, 1} expectedValues := []any{"bar", 28, 100, "baz", "28", "100", "baz", "baz"} + expectedValuesFromNewest := []any{"baz", "baz", "100", "28", "baz", 100, 28, "bar"} var keys []int var values []any - for k, v := range om.All() { + for k, v := range om.FromOldest() { keys = append(keys, k) values = append(values, v) } @@ -408,19 +410,45 @@ func TestIterators(t *testing.T) { assert.Equal(t, expectedKeys, keys) assert.Equal(t, expectedValues, values) + keys, values = []int{}, []any{} + + for k, v := range om.FromNewest() { + keys = append(keys, k) + values = append(values, v) + } + + assert.Equal(t, expectedKeysFromNewest, keys) + assert.Equal(t, expectedValuesFromNewest, values) + keys = []int{} - for k := range om.Keys() { + for k := range om.KeysFromOldest() { keys = append(keys, k) } assert.Equal(t, expectedKeys, keys) + keys = []int{} + + for k := range om.KeysFromNewest() { + keys = append(keys, k) + } + + assert.Equal(t, expectedKeysFromNewest, keys) + values = []any{} - for v := range om.Values() { + for v := range om.ValuesFromOldest() { values = append(values, v) } assert.Equal(t, expectedValues, values) + + values = []any{} + + for v := range om.ValuesFromNewest() { + values = append(values, v) + } + + assert.Equal(t, expectedValuesFromNewest, values) } From 2f1629387283f3564df35c9ace074e2e6f32fe21 Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Tue, 13 Aug 2024 21:28:17 +0100 Subject: [PATCH 3/3] feat: add support for From helper method --- README.md | 20 ++++++++++++++++++++ orderedmap.go | 11 +++++++++++ orderedmap_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/README.md b/README.md index c9ca813..ebd8a4d 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,26 @@ for k := range om.KeysNewest() { // 1 ``` +`From` is a convenience function that creates a new `OrderedMap` from an iterator over key-value pairs. + +```go +om := orderedmap.New[int, string]() +om.Set(1, "foo") +om.Set(2, "bar") +om.Set(3, "baz") + +om2 := orderedmap.From(om.FromOldest()) + +for k, v := range om2.FromOldest() { + fmt.Printf("%d => %s\n", k, v) +} + +// prints: +// 1 => foo +// 2 => bar +// 3 => baz +``` + ## Alternatives There are several other ordered map golang implementations out there, but I believe that at the time of writing none of them offer the same functionality as this library; more specifically: diff --git a/orderedmap.go b/orderedmap.go index a7f9500..6fe032b 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -360,3 +360,14 @@ func (om *OrderedMap[K, V]) ValuesFromNewest() iter.Seq[V] { } } } + +// From creates a new OrderedMap from an iterator over key-value pairs. +func From[K comparable, V any](i iter.Seq2[K, V]) *OrderedMap[K, V] { + om := New[K, V]() + + for k, v := range i { + om.Set(k, v) + } + + return om +} diff --git a/orderedmap_test.go b/orderedmap_test.go index b9d6754..2d9660f 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -452,3 +452,47 @@ func TestIterators(t *testing.T) { assert.Equal(t, expectedValuesFromNewest, values) } + +func TestIteratorsFrom(t *testing.T) { + om := New[int, any]() + om.Set(1, "bar") + om.Set(2, 28) + om.Set(3, 100) + om.Set(4, "baz") + om.Set(5, "28") + om.Set(6, "100") + om.Set(7, "baz") + om.Set(8, "baz") + + om2 := From(om.FromOldest()) + + expectedKeys := []int{1, 2, 3, 4, 5, 6, 7, 8} + expectedValues := []any{"bar", 28, 100, "baz", "28", "100", "baz", "baz"} + + var keys []int + var values []any + + for k, v := range om2.FromOldest() { + keys = append(keys, k) + values = append(values, v) + } + + assert.Equal(t, expectedKeys, keys) + assert.Equal(t, expectedValues, values) + + expectedKeysFromNewest := []int{8, 7, 6, 5, 4, 3, 2, 1} + expectedValuesFromNewest := []any{"baz", "baz", "100", "28", "baz", 100, 28, "bar"} + + om2 = From(om.FromNewest()) + + keys = []int{} + values = []any{} + + for k, v := range om2.FromOldest() { + keys = append(keys, k) + values = append(values, v) + } + + assert.Equal(t, expectedKeysFromNewest, keys) + assert.Equal(t, expectedValuesFromNewest, values) +}