Skip to content
Merged

v3 #46

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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
pull_request:

jobs:
unit-tests:
unit-tests-v2:
strategy:
matrix:
go:
Expand All @@ -19,3 +19,12 @@ jobs:
go-version: ${{ matrix.go }}
- run: go test -v ./...
- run: cd v2 && go test -v ./...

unit-tests-v3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.23'
- run: cd v3 && go test -v ./...
67 changes: 38 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# 🔃 github.com/elliotchance/orderedmap/v2 [![GoDoc](https://godoc.org/github.com/elliotchance/orderedmap/v2?status.svg)](https://godoc.org/github.com/elliotchance/orderedmap/v2)
# 🔃 github.com/elliotchance/orderedmap/v3 [![GoDoc](https://godoc.org/github.com/elliotchance/orderedmap/v3?status.svg)](https://godoc.org/github.com/elliotchance/orderedmap/v3)

## Basic Usage

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

```go
import "github.com/elliotchance/orderedmap/v2"
import "github.com/elliotchance/orderedmap/v3"

func main() {
m := orderedmap.NewOrderedMap[string, any]()
Expand All @@ -19,26 +19,55 @@ func main() {
}
```

*Note: v2 requires Go v1.18 for generics.* If you need to support Go 1.17 or
below, you can use v1.
> [!NOTE]
>
> - _v3 requires Go v1.23_ - If you need to support Go 1.18-1.22, you can use v2.
> - _v2 requires Go v1.18 for generics_ - If you need to support Go 1.17 or below, you can use v1.

Internally an `*OrderedMap` uses the composite type
[map](https://go.dev/blog/maps) combined with a
trimmed down linked list to maintain the order.

## 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:
The following methods all return
[iterators](https://go.dev/doc/go1.23#iterators) that can be used to loop over
elements in an ordered map:

- `AllFromFront()`
- `AllFromBack()`
- `Keys()`
- `Values()`

```go
for _, key := range m.Keys() {
value, _:= m.Get(key)
// Iterate through all elements from oldest to newest:
for key, value := range m.AllFromFront() {
fmt.Println(key, value)
}
```

For larger maps you should use `Front()` or `Back()` to iterate per element:
Iterators are safe to use bidirectionally, and will return `nil` once it goes
beyond the first or last item. If the map is changing while the iteration is
in-flight it may produce unexpected behavior.

If you want to get a slice of the map keys or values, you can use the standard
`slices.Collect` method with the iterator returned from `Keys()` or `Values()`:

```go
fmt.Println(slices.Collect(m.Keys())
// [A B C]
```

Likewise, calling `maps.Collect` on the iterator returned from `AllFromFront()`
will create a regular unordered map from the ordered one:

```go
fmt.Println(maps.Collect(m.AllFromFront())
// [A:1 B:2 C:3]
```

If you don't want to use iterators, you can also manually loop over the elements
using `Front()` or `Back()` with `Next()`:

```go
// Iterate through all elements from oldest to newest:
Expand All @@ -51,23 +80,3 @@ for el := m.Back(); el != nil; el = el.Prev() {
fmt.Println(el.Key, el.Value)
}
```

In case you're using Go 1.23, you can also [iterate with
`range`](https://go.dev/doc/go1.23#iterators) by using `Iterator()` or
`ReverseIterator()` methods:

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

for key, value := range m.ReverseIterator() {
fmt.Println(key, value)
}
```

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

If the map is changing while the iteration is in-flight it may produce
unexpected behavior.
11 changes: 11 additions & 0 deletions v3/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/elliotchance/orderedmap/v3

go 1.23.0

require github.com/stretchr/testify v1.7.1

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
11 changes: 11 additions & 0 deletions v3/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
95 changes: 95 additions & 0 deletions v3/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package orderedmap

// Element is an element of a null terminated (non circular) intrusive doubly linked list that contains the key of the correspondent element in the ordered map too.
type Element[K comparable, V any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[K, V]

// The key that corresponds to this element in the ordered map.
Key K

// The value stored with this element.
Value V
}

// Next returns the next list element or nil.
func (e *Element[K, V]) Next() *Element[K, V] {
return e.next
}

// Prev returns the previous list element or nil.
func (e *Element[K, V]) Prev() *Element[K, V] {
return e.prev
}

// list represents a null terminated (non circular) intrusive doubly linked list.
// The list is immediately usable after instantiation without the need of a dedicated initialization.
type list[K comparable, V any] struct {
root Element[K, V] // list head and tail
}

func (l *list[K, V]) IsEmpty() bool {
return l.root.next == nil
}

// Front returns the first element of list l or nil if the list is empty.
func (l *list[K, V]) Front() *Element[K, V] {
return l.root.next
}

// Back returns the last element of list l or nil if the list is empty.
func (l *list[K, V]) Back() *Element[K, V] {
return l.root.prev
}

// Remove removes e from its list
func (l *list[K, V]) Remove(e *Element[K, V]) {
if e.prev == nil {
l.root.next = e.next
} else {
e.prev.next = e.next
}
if e.next == nil {
l.root.prev = e.prev
} else {
e.next.prev = e.prev
}
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
}

// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *list[K, V]) PushFront(key K, value V) *Element[K, V] {
e := &Element[K, V]{Key: key, Value: value}
if l.root.next == nil {
// It's the first element
l.root.next = e
l.root.prev = e
return e
}

e.next = l.root.next
l.root.next.prev = e
l.root.next = e
return e
}

// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *list[K, V]) PushBack(key K, value V) *Element[K, V] {
e := &Element[K, V]{Key: key, Value: value}
if l.root.prev == nil {
// It's the first element
l.root.next = e
l.root.prev = e
return e
}

e.prev = l.root.prev
l.root.prev.next = e
l.root.prev = e
return e
}
Loading
Loading