Skip to content
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
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# 🔃 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)

The `orderedmap` package provides a high performance ordered map in Go:
## Installation

```bash
go get -u github.com/elliotchance/orderedmap
```

## Basic Usage

An `*OrderedMap` is a high performance ordered map that maintains amortized O(1)
for `Set`, `Get`, `Delete` and `Len`:

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

m.Delete("qux")
```

Internally an `*OrderedMap` uses a combination of a map and linked list.

## Iterating

Be careful using `Keys()` as it will create a copy of all of the keys so it's
only suitable for a small number of items:

```go
for _, key := range m.Keys() {
value, _:= m.Get(key)
fmt.Println(key, value)
}
```

Internally an `*OrderedMap` uses a combination of a map and linked list to
maintain amortized O(1) for `Set`, `Get`, `Delete` and `Len`.
For larger maps you should use `Front()` or `Back()` to iterate per element:

```go
// Iterate through all elements from oldest to newest:
for el := m.Front(); el != nil; el = el.Next() {
fmt.Println(el.Key, el.Value)
}

// You can also use Back and Prev to iterate in reverse:
for el := m.Back(); el != nil; el = el.Prev() {
fmt.Println(el.Key, el.Value)
}
```

The iterator is safe to use bidirectionally, and will return `nil` once it goes
beyond the first or last item.

See the full documentation at
[https://godoc.org/github.com/elliotchance/orderedmap](https://godoc.org/github.com/elliotchance/orderedmap).
If the map is changing while the iteration is in-flight it may produce
unexpected behavior.
33 changes: 33 additions & 0 deletions element.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package orderedmap

import "container/list"

type Element struct {
Key, Value interface{}

element *list.Element
}

func newElement(e *list.Element) *Element {
if e == nil {
return nil
}

element := e.Value.(*orderedMapElement)

return &Element{
element: e,
Key: element.key,
Value: element.value,
}
}

// Next returns the next element, or nil if it finished.
func (e *Element) Next() *Element {
return newElement(e.element.Next())
}

// Prev returns the previous element, or nil if it finished.
func (e *Element) Prev() *Element {
return newElement(e.element.Prev())
}
67 changes: 67 additions & 0 deletions element_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package orderedmap_test

import (
"github.com/elliotchance/orderedmap"
"github.com/stretchr/testify/assert"
"testing"
)

func TestElement_Key(t *testing.T) {
t.Run("Front", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
assert.Equal(t, 1, m.Front().Key)
})

t.Run("Back", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
assert.Equal(t, 2, m.Back().Key)
})
}

func TestElement_Value(t *testing.T) {
t.Run("Front", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
assert.Equal(t, "foo", m.Front().Value)
})

t.Run("Back", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
assert.Equal(t, "bar", m.Back().Value)
})
}

func TestElement_Next(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
m.Set(3, "baz")

var results []interface{}
for el := m.Front(); el != nil; el = el.Next() {
results = append(results, el.Key, el.Value)
}

assert.Equal(t, []interface{}{1, "foo", 2, "bar", 3, "baz"}, results)
}

func TestElement_Prev(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, "foo")
m.Set(2, "bar")
m.Set(3, "baz")

var results []interface{}
for el := m.Back(); el != nil; el = el.Prev() {
results = append(results, el.Key, el.Value)
}

assert.Equal(t, []interface{}{3, "baz", 2, "bar", 1, "foo"}, results)
}
34 changes: 34 additions & 0 deletions orderedmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,37 @@ func (m *OrderedMap) Delete(key interface{}) (didDelete bool) {

return ok
}

// Front will return the element that is the first (oldest Set element). If
// there are no elements this will return nil.
func (m *OrderedMap) Front() *Element {
front := m.ll.Front()
if front == nil {
return nil
}

element := front.Value.(*orderedMapElement)

return &Element{
element: front,
Key: element.key,
Value: element.value,
}
}

// Back will return the element that is the last (most recent Set element). If
// there are no elements this will return nil.
func (m *OrderedMap) Back() *Element {
back := m.ll.Back()
if back == nil {
return nil
}

element := back.Value.(*orderedMapElement)

return &Element{
element: back,
Key: element.key,
Value: element.value,
}
}
36 changes: 36 additions & 0 deletions orderedmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,32 @@ func TestDelete(t *testing.T) {
})
}

func TestOrderedMap_Front(t *testing.T) {
t.Run("NilOnEmptyMap", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
assert.Nil(t, m.Front())
})

t.Run("NilOnEmptyMap", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, true)
assert.NotNil(t, m.Front())
})
}

func TestOrderedMap_Back(t *testing.T) {
t.Run("NilOnEmptyMap", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
assert.Nil(t, m.Back())
})

t.Run("NilOnEmptyMap", func(t *testing.T) {
m := orderedmap.NewOrderedMap()
m.Set(1, true)
assert.NotNil(t, m.Back())
})
}

func benchmarkOrderedMap_Set(multiplier int) func(b *testing.B) {
return func(b *testing.B) {
m := orderedmap.NewOrderedMap()
Expand Down Expand Up @@ -377,3 +403,13 @@ func ExampleNewOrderedMap() {
fmt.Println(key, value)
}
}

func ExampleOrderedMap_Front() {
m := orderedmap.NewOrderedMap()
m.Set(1, true)
m.Set(2, true)

for el := m.Front(); el != nil; el = el.Next() {
fmt.Println(el)
}
}