Skip to content

Commit d95317e

Browse files
committed
Added bidirectional iterator
The new methods Front() and Back() can be used to bidirectionally iterate over the elements. If the map is changing while the iteration is in-flight it may produce unexpected behavior.
1 parent 887596e commit d95317e

File tree

5 files changed

+200
-5
lines changed

5 files changed

+200
-5
lines changed

README.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 🔃 github.com/elliotchance/orderedmap [![GoDoc](https://godoc.org/github.com/elliotchance/orderedmap?status.svg)](https://godoc.org/github.com/elliotchance/orderedmap) [![Build Status](https://travis-ci.org/elliotchance/orderedmap.svg?branch=master)](https://travis-ci.org/elliotchance/orderedmap)
22

3-
The `orderedmap` package provides a high performance ordered map in Go:
3+
An `*OrderedMap` is a high performance ordered map that maintains amortized O(1)
4+
for `Set`, `Get`, `Delete` and `Len`:
45

56
```go
67
m := orderedmap.NewOrderedMap()
@@ -10,15 +11,38 @@ m.Set("qux", 1.23)
1011
m.Set(123, true)
1112

1213
m.Delete("qux")
14+
```
15+
16+
Internally an `*OrderedMap` uses a combination of a map and linked list.
17+
18+
# Iterating
1319

20+
Be careful using `Keys()` as it will create a copy of all of the keys so it's
21+
only suitable for a small number of items:
22+
23+
```go
1424
for _, key := range m.Keys() {
1525
value, _:= m.Get(key)
1626
fmt.Println(key, value)
1727
}
1828
```
1929

20-
Internally an `*OrderedMap` uses a combination of a map and linked list to
21-
maintain amortized O(1) for `Set`, `Get`, `Delete` and `Len`.
30+
For larger maps you should use `Front()` or `Back()` to iterate per element:
31+
32+
```go
33+
// Iterate through all elements from oldest to newest:
34+
for el := m.Front(); el != nil; el = el.Next() {
35+
fmt.Println(el)
36+
}
37+
38+
// You can also use Back and Prev to iterate in reverse:
39+
for el := m.Back(); el != nil; el = el.Prev() {
40+
fmt.Println(el)
41+
}
42+
```
43+
44+
The iterator is safe to use bidirectionally, and will return `nil` once it goes
45+
beyond the first or last item.
2246

23-
See the full documentation at
24-
[https://godoc.org/github.com/elliotchance/orderedmap](https://godoc.org/github.com/elliotchance/orderedmap).
47+
If the map is changing while the iteration is in-flight it may produce
48+
unexpected behavior.

element.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package orderedmap
2+
3+
import "container/list"
4+
5+
type Element struct {
6+
Key, Value interface{}
7+
8+
element *list.Element
9+
}
10+
11+
func newElement(e *list.Element) *Element {
12+
if e == nil {
13+
return nil
14+
}
15+
16+
element := e.Value.(*orderedMapElement)
17+
18+
return &Element{
19+
element: e,
20+
Key: element.key,
21+
Value: element.value,
22+
}
23+
}
24+
25+
// Next returns the next element, or nil if it finished.
26+
func (e *Element) Next() *Element {
27+
return newElement(e.element.Next())
28+
}
29+
30+
// Prev returns the previous element, or nil if it finished.
31+
func (e *Element) Prev() *Element {
32+
return newElement(e.element.Prev())
33+
}

element_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package orderedmap_test
2+
3+
import (
4+
"fmt"
5+
"github.com/elliotchance/orderedmap"
6+
"github.com/stretchr/testify/assert"
7+
"testing"
8+
)
9+
10+
func TestElement_Key(t *testing.T) {
11+
t.Run("Front", func(t *testing.T) {
12+
m := orderedmap.NewOrderedMap()
13+
m.Set(1, "foo")
14+
m.Set(2, "bar")
15+
assert.Equal(t, 1, m.Front().Key)
16+
})
17+
18+
t.Run("Back", func(t *testing.T) {
19+
m := orderedmap.NewOrderedMap()
20+
m.Set(1, "foo")
21+
m.Set(2, "bar")
22+
assert.Equal(t, 2, m.Back().Key)
23+
})
24+
}
25+
26+
func TestElement_Value(t *testing.T) {
27+
t.Run("Front", func(t *testing.T) {
28+
m := orderedmap.NewOrderedMap()
29+
m.Set(1, "foo")
30+
m.Set(2, "bar")
31+
assert.Equal(t, "foo", m.Front().Value)
32+
})
33+
34+
t.Run("Back", func(t *testing.T) {
35+
m := orderedmap.NewOrderedMap()
36+
m.Set(1, "foo")
37+
m.Set(2, "bar")
38+
assert.Equal(t, "bar", m.Back().Value)
39+
})
40+
}
41+
42+
func TestElement_Next(t *testing.T) {
43+
m := orderedmap.NewOrderedMap()
44+
m.Set(1, "foo")
45+
m.Set(2, "bar")
46+
m.Set(3, "baz")
47+
48+
var results []interface{}
49+
for el := m.Front(); el != nil; el = el.Next() {
50+
results = append(results, el.Key, el.Value)
51+
}
52+
53+
assert.Equal(t, []interface{}{1, "foo", 2, "bar", 3, "baz"}, results)
54+
}
55+
56+
func TestElement_Prev(t *testing.T) {
57+
m := orderedmap.NewOrderedMap()
58+
m.Set(1, "foo")
59+
m.Set(2, "bar")
60+
m.Set(3, "baz")
61+
62+
var results []interface{}
63+
for el := m.Back(); el != nil; el = el.Prev() {
64+
results = append(results, el.Key, el.Value)
65+
}
66+
67+
assert.Equal(t, []interface{}{3, "baz", 2, "bar", 1, "foo"}, results)
68+
}
69+
70+
func ExampleMapIterator_Next() {
71+
m := orderedmap.NewOrderedMap()
72+
m.Set(1, true)
73+
m.Set(2, true)
74+
75+
for el := m.Front(); el != nil; el = el.Next() {
76+
fmt.Println(el)
77+
}
78+
}

orderedmap.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,37 @@ func (m *OrderedMap) Delete(key interface{}) (didDelete bool) {
7676

7777
return ok
7878
}
79+
80+
// Front will return the element that is the first (oldest Set element). If
81+
// there are no elements this will return nil.
82+
func (m *OrderedMap) Front() *Element {
83+
front := m.ll.Front()
84+
if front == nil {
85+
return nil
86+
}
87+
88+
element := front.Value.(*orderedMapElement)
89+
90+
return &Element{
91+
element: front,
92+
Key: element.key,
93+
Value: element.value,
94+
}
95+
}
96+
97+
// Back will return the element that is the last (most recent Set element). If
98+
// there are no elements this will return nil.
99+
func (m *OrderedMap) Back() *Element {
100+
back := m.ll.Back()
101+
if back == nil {
102+
return nil
103+
}
104+
105+
element := back.Value.(*orderedMapElement)
106+
107+
return &Element{
108+
element: back,
109+
Key: element.key,
110+
Value: element.value,
111+
}
112+
}

orderedmap_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,32 @@ func TestDelete(t *testing.T) {
276276
})
277277
}
278278

279+
func TestOrderedMap_Front(t *testing.T) {
280+
t.Run("NilOnEmptyMap", func(t *testing.T) {
281+
m := orderedmap.NewOrderedMap()
282+
assert.Nil(t, m.Front())
283+
})
284+
285+
t.Run("NilOnEmptyMap", func(t *testing.T) {
286+
m := orderedmap.NewOrderedMap()
287+
m.Set(1, true)
288+
assert.NotNil(t, m.Front())
289+
})
290+
}
291+
292+
func TestOrderedMap_Back(t *testing.T) {
293+
t.Run("NilOnEmptyMap", func(t *testing.T) {
294+
m := orderedmap.NewOrderedMap()
295+
assert.Nil(t, m.Back())
296+
})
297+
298+
t.Run("NilOnEmptyMap", func(t *testing.T) {
299+
m := orderedmap.NewOrderedMap()
300+
m.Set(1, true)
301+
assert.NotNil(t, m.Back())
302+
})
303+
}
304+
279305
func benchmarkOrderedMap_Set(multiplier int) func(b *testing.B) {
280306
return func(b *testing.B) {
281307
m := orderedmap.NewOrderedMap()

0 commit comments

Comments
 (0)