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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ Len() int
ToSlice() []T
```

## orderedset package

This package provides an OrderedSet struct with the following methods:

```go
Add(values ...T)
AddSlice(slice []T)
Remove(value T)
RemoveSlice(slice []T)
Includes(value T) bool
Len() int
ToSliceFromOldest() []T
ToSliceFromNewest() []T
```

The difference to Set is that the insertion order of the values is preserved.

## maps package

Provides some helper methods for maps:
Expand Down
12 changes: 11 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@ module github.com/jesseduffield/generics

go 1.18

require golang.org/x/exp v0.0.0-20220317015231-48e79f11773a
require (
github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/exp v0.0.0-20220317015231-48e79f11773a
)

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
golang.org/x/exp v0.0.0-20220317015231-48e79f11773a h1:DAzrdbxsb5tXNOhMCSwF7ZdfMbW46hE9fSVO6BsmUZM=
golang.org/x/exp v0.0.0-20220317015231-48e79f11773a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4 changes: 2 additions & 2 deletions maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ func Values[Key comparable, Value any](m map[Key]Value) []Value {
func TransformValues[Key comparable, Value any, NewValue any](
m map[Key]Value, fn func(Value) NewValue,
) map[Key]NewValue {
output := make(map[Key]NewValue)
output := make(map[Key]NewValue, len(m))
for key, value := range m {
output[key] = fn(value)
}
return output
}

func TransformKeys[Key comparable, Value any, NewKey comparable](m map[Key]Value, fn func(Key) NewKey) map[NewKey]Value {
output := make(map[NewKey]Value)
output := make(map[NewKey]Value, len(m))
for key, value := range m {
output[fn(key)] = value
}
Expand Down
65 changes: 65 additions & 0 deletions orderedset/orderedset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package orderedset

import (
orderedmap "github.com/wk8/go-ordered-map/v2"
)

type OrderedSet[T comparable] struct {
om *orderedmap.OrderedMap[T, bool]
}

func New[T comparable]() *OrderedSet[T] {
return &OrderedSet[T]{om: orderedmap.New[T, bool]()}
}

func NewFromSlice[T comparable](slice []T) *OrderedSet[T] {
result := &OrderedSet[T]{om: orderedmap.New[T, bool](len(slice))}
result.Add(slice...)
return result
}

func (os *OrderedSet[T]) Add(values ...T) {
for _, value := range values {
os.om.Set(value, true)
}
}

func (os *OrderedSet[T]) Remove(value T) {
os.om.Delete(value)
}

func (os *OrderedSet[T]) RemoveSlice(slice []T) {
for _, value := range slice {
os.Remove(value)
}
}

func (os *OrderedSet[T]) Includes(value T) bool {
return os.om.Value(value)
}

func (os *OrderedSet[T]) Len() int {
return os.om.Len()
}

func (os *OrderedSet[T]) ToSliceFromOldest() []T {
// TODO: can be simplified to
// return os.om.KeysFromOldest()
// when we update to a newer version of go-ordered-map
result := make([]T, 0, os.Len())
for pair := os.om.Oldest(); pair != nil; pair = pair.Next() {
result = append(result, pair.Key)
}
return result
}

func (os *OrderedSet[T]) ToSliceFromNewest() []T {
// TODO: can be simplified to
// return os.om.KeysFromNewest()
// when we update to a newer version of go-ordered-map
result := make([]T, 0, os.Len())
for pair := os.om.Newest(); pair != nil; pair = pair.Prev() {
result = append(result, pair.Key)
}
return result
}
103 changes: 103 additions & 0 deletions orderedset/orderedset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package orderedset

import (
"testing"

"github.com/jesseduffield/generics/internal/testutils"
)

func TestAddIncludes(t *testing.T) {
os := New[int]()
os.Add(1)
if !os.Includes(1) {
t.Errorf("Add(1) failed: Includes(1) returned false")
}

if os.Includes(0) {
t.Errorf("Add(1) failed: Includes(0) returned true")
}
}

func TestAddSliceIncludes(t *testing.T) {
os := New[int]()
os.Add(1, 2)
if !os.Includes(1) {
t.Errorf("AddSlice failed: Includes(1) returned false")
}

if !os.Includes(2) {
t.Errorf("AddSlice failed: Includes(2) returned false")
}

if os.Includes(3) {
t.Errorf("AddSlice failed: Includes(3) returned true")
}
}

func TestRemoveIncludes(t *testing.T) {
os := NewFromSlice([]int{1, 2})
os.Remove(1)
if os.Includes(1) {
t.Errorf("Remove failed: Includes(1) returned true")
}

if !os.Includes(2) {
t.Errorf("Remove failed: Includes(2) returned false")
}
}

func TestRemoveSliceIncludes(t *testing.T) {
os := NewFromSlice([]int{1, 2, 3})
os.RemoveSlice([]int{1, 2})
if os.Includes(1) {
t.Errorf("Remove failed: Includes(1) returned true")
}

if os.Includes(2) {
t.Errorf("Remove failed: Includes(2) returned true")
}

if !os.Includes(3) {
t.Errorf("Remove failed: Includes(3) returned false")
}
}

func TestNewFromSlice(t *testing.T) {
os := NewFromSlice([]int{1, 2})
if !os.Includes(1) {
t.Errorf("NewFromSlice failed: Includes(1) returned false")
}

if !os.Includes(2) {
t.Errorf("NewFromSlice failed: Includes(2) returned false")
}

if os.Includes(3) {
t.Errorf("NewFromSlice failed: Includes(3) returned true")
}
}

func TestLen(t *testing.T) {
set := NewFromSlice([]int{})
if set.Len() != 0 {
t.Errorf("Len() = %v, expected %v", set.Len(), 0)
}

set = NewFromSlice([]int{1})
if set.Len() != 1 {
t.Errorf("Len() = %v, expected %v", set.Len(), 1)
}
}

func TestToSlice(t *testing.T) {
set := New[int]()
set.Add(1)
set.Add(3)
set.Add(2)

slice := set.ToSliceFromOldest()
testutils.ExpectSlice(t, []int{1, 3, 2}, slice)

slice = set.ToSliceFromNewest()
testutils.ExpectSlice(t, []int{2, 3, 1}, slice)
}
9 changes: 3 additions & 6 deletions set/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ func New[T comparable]() *Set[T] {
}

func NewFromSlice[T comparable](slice []T) *Set[T] {
hashMap := make(map[T]bool)
for _, value := range slice {
hashMap[value] = true
}

return &Set[T]{hashMap: hashMap}
result := &Set[T]{hashMap: make(map[T]bool, len(slice))}
result.Add(slice...)
return result
}

func (s *Set[T]) Add(values ...T) {
Expand Down
6 changes: 3 additions & 3 deletions slices/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func MapInPlace[T any](slice []T, f func(T) T) {

// Produces a new slice, leaves the input slice untouched.
func Filter[T any](slice []T, test func(T) bool) []T {
result := make([]T, 0)
result := make([]T, 0, len(slice))
for _, element := range slice {
if test(element) {
result = append(result, element)
Expand All @@ -134,7 +134,7 @@ func FilterWithIndex[T any](slice []T, f func(T, int) bool) []T {
}

func TryFilter[T any](slice []T, test func(T) (bool, error)) ([]T, error) {
result := make([]T, 0)
result := make([]T, 0, len(slice))
for _, element := range slice {
ok, err := test(element)
if err != nil {
Expand All @@ -148,7 +148,7 @@ func TryFilter[T any](slice []T, test func(T) (bool, error)) ([]T, error) {
}

func TryFilterWithIndex[T any](slice []T, test func(T, int) (bool, error)) ([]T, error) {
result := make([]T, 0)
result := make([]T, 0, len(slice))
for i, element := range slice {
ok, err := test(element, i)
if err != nil {
Expand Down