Skip to content

Commit c843b30

Browse files
authored
Added bidirectional iterator (#2)
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 c843b30

File tree

5 files changed

+207
-5
lines changed

5 files changed

+207
-5
lines changed

README.md

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
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+
## Installation
4+
5+
```bash
6+
go get -u github.com/elliotchance/orderedmap
7+
```
8+
9+
## Basic Usage
10+
11+
An `*OrderedMap` is a high performance ordered map that maintains amortized O(1)
12+
for `Set`, `Get`, `Delete` and `Len`:
413

514
```go
615
m := orderedmap.NewOrderedMap()
@@ -10,15 +19,38 @@ m.Set("qux", 1.23)
1019
m.Set(123, true)
1120

1221
m.Delete("qux")
22+
```
23+
24+
Internally an `*OrderedMap` uses a combination of a map and linked list.
25+
26+
## Iterating
27+
28+
Be careful using `Keys()` as it will create a copy of all of the keys so it's
29+
only suitable for a small number of items:
1330

31+
```go
1432
for _, key := range m.Keys() {
1533
value, _:= m.Get(key)
1634
fmt.Println(key, value)
1735
}
1836
```
1937

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`.
38+
For larger maps you should use `Front()` or `Back()` to iterate per element:
39+
40+
```go
41+
// Iterate through all elements from oldest to newest:
42+
for el := m.Front(); el != nil; el = el.Next() {
43+
fmt.Println(el.Key, el.Value)
44+
}
45+
46+
// You can also use Back and Prev to iterate in reverse:
47+
for el := m.Back(); el != nil; el = el.Prev() {
48+
fmt.Println(el.Key, el.Value)
49+
}
50+
```
51+
52+
The iterator is safe to use bidirectionally, and will return `nil` once it goes
53+
beyond the first or last item.
2254

23-
See the full documentation at
24-
[https://godoc.org/github.com/elliotchance/orderedmap](https://godoc.org/github.com/elliotchance/orderedmap).
55+
If the map is changing while the iteration is in-flight it may produce
56+
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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package orderedmap_test
2+
3+
import (
4+
"github.com/elliotchance/orderedmap"
5+
"github.com/stretchr/testify/assert"
6+
"testing"
7+
)
8+
9+
func TestElement_Key(t *testing.T) {
10+
t.Run("Front", func(t *testing.T) {
11+
m := orderedmap.NewOrderedMap()
12+
m.Set(1, "foo")
13+
m.Set(2, "bar")
14+
assert.Equal(t, 1, m.Front().Key)
15+
})
16+
17+
t.Run("Back", func(t *testing.T) {
18+
m := orderedmap.NewOrderedMap()
19+
m.Set(1, "foo")
20+
m.Set(2, "bar")
21+
assert.Equal(t, 2, m.Back().Key)
22+
})
23+
}
24+
25+
func TestElement_Value(t *testing.T) {
26+
t.Run("Front", func(t *testing.T) {
27+
m := orderedmap.NewOrderedMap()
28+
m.Set(1, "foo")
29+
m.Set(2, "bar")
30+
assert.Equal(t, "foo", m.Front().Value)
31+
})
32+
33+
t.Run("Back", func(t *testing.T) {
34+
m := orderedmap.NewOrderedMap()
35+
m.Set(1, "foo")
36+
m.Set(2, "bar")
37+
assert.Equal(t, "bar", m.Back().Value)
38+
})
39+
}
40+
41+
func TestElement_Next(t *testing.T) {
42+
m := orderedmap.NewOrderedMap()
43+
m.Set(1, "foo")
44+
m.Set(2, "bar")
45+
m.Set(3, "baz")
46+
47+
var results []interface{}
48+
for el := m.Front(); el != nil; el = el.Next() {
49+
results = append(results, el.Key, el.Value)
50+
}
51+
52+
assert.Equal(t, []interface{}{1, "foo", 2, "bar", 3, "baz"}, results)
53+
}
54+
55+
func TestElement_Prev(t *testing.T) {
56+
m := orderedmap.NewOrderedMap()
57+
m.Set(1, "foo")
58+
m.Set(2, "bar")
59+
m.Set(3, "baz")
60+
61+
var results []interface{}
62+
for el := m.Back(); el != nil; el = el.Prev() {
63+
results = append(results, el.Key, el.Value)
64+
}
65+
66+
assert.Equal(t, []interface{}{3, "baz", 2, "bar", 1, "foo"}, results)
67+
}

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: 36 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()
@@ -377,3 +403,13 @@ func ExampleNewOrderedMap() {
377403
fmt.Println(key, value)
378404
}
379405
}
406+
407+
func ExampleOrderedMap_Front() {
408+
m := orderedmap.NewOrderedMap()
409+
m.Set(1, true)
410+
m.Set(2, true)
411+
412+
for el := m.Front(); el != nil; el = el.Next() {
413+
fmt.Println(el)
414+
}
415+
}

0 commit comments

Comments
 (0)