Skip to content

Commit ab781dd

Browse files
author
Tom Godkin
committed
Implement LiftHashMap
1 parent 0c7c260 commit ab781dd

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

iter/lift.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package iter
22

3-
import "github.com/BooleanCat/go-functional/option"
3+
import (
4+
"sync"
5+
6+
"github.com/BooleanCat/go-functional/option"
7+
)
48

59
// LiftIter implements `Lift`. See `Lift`'s documentation.
610
type LiftIter[T any] struct {
@@ -26,3 +30,64 @@ func (iter *LiftIter[T]) Next() option.Option[T] {
2630
}
2731

2832
var _ Iterator[struct{}] = new(LiftIter[struct{}])
33+
34+
// LiftHashMapIter implements `LiftHashMap`. See `LiftHashMap`'s documentation.
35+
type LiftHashMapIter[T comparable, U any] struct {
36+
hashmap map[T]U
37+
items chan Tuple[T, U]
38+
stopOnce sync.Once
39+
stop chan struct{}
40+
}
41+
42+
// LiftHashMap instantiates a `LiftHashMapIter` that will yield all items in
43+
// the provided map in the form iter.Tuple[key, value].
44+
//
45+
// Unlike most iterators, `LiftHashMap` should be closed after usage (because).
46+
// The iterator is closed when any of the two conditions are met.
47+
//
48+
// 1. The caller explicitly invokes the `Close` method.
49+
// 2. The iterator is exhausted.
50+
//
51+
// It is safe to call Close multiple times or after exhaustion. It is not
52+
// necessary to call Close if exhaustion is guaranteed, but may be wise to
53+
// redundantly call Close if you're unsure.
54+
func LiftHashMap[T comparable, U any](hashmap map[T]U) *LiftHashMapIter[T, U] {
55+
iter := &LiftHashMapIter[T, U]{hashmap, make(chan Tuple[T, U]), sync.Once{}, make(chan struct{})}
56+
57+
go func() {
58+
defer close(iter.items)
59+
defer iter.stopOnce.Do(func() { close(iter.stop) })
60+
outer:
61+
for k, v := range hashmap {
62+
select {
63+
case iter.items <- Tuple[T, U]{k, v}:
64+
continue
65+
case <-iter.stop:
66+
break outer
67+
}
68+
69+
}
70+
}()
71+
72+
return iter
73+
}
74+
75+
// Close the iterator. See `LiftHashMap` documentation for details.
76+
func (iter *LiftHashMapIter[T, U]) Close() {
77+
iter.stopOnce.Do(func() {
78+
iter.stop <- struct{}{}
79+
close(iter.stop)
80+
})
81+
}
82+
83+
// Next implements the Iterator interface for `LiftHashMap`.
84+
func (iter *LiftHashMapIter[T, U]) Next() option.Option[Tuple[T, U]] {
85+
pair, ok := <-iter.items
86+
if !ok {
87+
return option.None[Tuple[T, U]]()
88+
}
89+
90+
return option.Some(pair)
91+
}
92+
93+
var _ Iterator[Tuple[struct{}, struct{}]] = new(LiftHashMapIter[struct{}, struct{}])

iter/lift_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package iter_test
22

33
import (
44
"fmt"
5+
"sort"
56
"testing"
67

78
"github.com/BooleanCat/go-functional/internal/assert"
@@ -25,3 +26,47 @@ func TestLift(t *testing.T) {
2526
func TestLiftEmpty(t *testing.T) {
2627
assert.True(t, iter.Lift([]int{}).Next().IsNone())
2728
}
29+
30+
func TestLiftHashMap(t *testing.T) {
31+
pokemon := make(map[string]string)
32+
pokemon["name"] = "pikachu"
33+
pokemon["type"] = "electric"
34+
35+
items := iter.Collect[iter.Tuple[string, string]](iter.LiftHashMap(pokemon))
36+
sort.Slice(items, func(i, j int) bool {
37+
return items[i].One < items[j].One
38+
})
39+
40+
assert.SliceEqual(t, items, []iter.Tuple[string, string]{{"name", "pikachu"}, {"type", "electric"}})
41+
}
42+
43+
func TestLiftHashMapCloseEarly(t *testing.T) {
44+
pokemon := make(map[string]string)
45+
pokemon["name"] = "pikachu"
46+
pokemon["type"] = "electric"
47+
48+
items := iter.LiftHashMap(pokemon)
49+
assert.True(t, items.Next().IsSome())
50+
items.Close()
51+
assert.True(t, items.Next().IsNone())
52+
}
53+
54+
func TestLiftHashMapCloseMultipleSafe(t *testing.T) {
55+
pokemon := make(map[string]string)
56+
pokemon["name"] = "pikachu"
57+
pokemon["type"] = "electric"
58+
59+
items := iter.LiftHashMap(pokemon)
60+
items.Close()
61+
items.Close()
62+
}
63+
64+
func TestLiftHashMapCloseAfterExhaustedSafe(t *testing.T) {
65+
pokemon := make(map[string]string)
66+
pokemon["name"] = "pikachu"
67+
pokemon["type"] = "electric"
68+
69+
items := iter.LiftHashMap(pokemon)
70+
iter.Collect[iter.Tuple[string, string]](items)
71+
items.Close()
72+
}

0 commit comments

Comments
 (0)