diff --git a/README.md b/README.md index b028944..ebd8a4d 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,57 @@ 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 +``` + +`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/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..6fe032b 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" ) @@ -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 @@ -294,3 +294,80 @@ func (om *OrderedMap[K, V]) GetAndMoveToFront(key K) (val V, err error) { return } + +// 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) { + return + } + } + } +} + +// 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) { + return + } + } + } +} + +// 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) { + return + } + } + } +} + +// 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 + } + } + } +} + +// 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 a3137eb..2d9660f 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -382,3 +382,117 @@ 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} + 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.FromOldest() { + keys = append(keys, k) + values = append(values, v) + } + + 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.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.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) +} + +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) +}