Skip to content

Commit

Permalink
Merge pull request #41 from speakeasy-api/iterators
Browse files Browse the repository at this point in the history
feat: add support for iterators introduced in go1.23
  • Loading branch information
wk8 authored Aug 15, 2024
2 parents 85ca4a2 + 2f16293 commit 6ea3647
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 4 deletions.
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)
}

0 comments on commit 6ea3647

Please sign in to comment.