Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for iterators introduced in go1.23 #41

Merged
merged 3 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
81 changes: 79 additions & 2 deletions orderedmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
114 changes: 114 additions & 0 deletions orderedmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}