Merge pull request #8653 from ProofOfKeags/fn-prim
DynComms [0/n]: `fn` package additions
This commit is contained in:
14 files changed
+1586
-65
No files matched your search
+3
-5
@@ -2,8 +2,6 @@ package fn
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/lightninglabs/neutrino/cache/lru"
|
||||
)
|
||||
|
||||
// ConcurrentQueue is a typed concurrent-safe FIFO queue with unbounded
|
||||
@@ -17,7 +15,7 @@ type ConcurrentQueue[T any] struct {
|
||||
|
||||
chanIn chan T
|
||||
chanOut chan T
|
||||
overflow *lru.List[T]
|
||||
overflow *List[T]
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
@@ -25,13 +23,13 @@ type ConcurrentQueue[T any] struct {
|
||||
|
||||
// NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is
|
||||
// the capacity of the output channel. When the size of the queue is below this
|
||||
// threshold, pushes do n[?12;4$yot incur the overhead of the less efficient overflow
|
||||
// threshold, pushes do not incur the overhead of the less efficient overflow
|
||||
// structure.
|
||||
func NewConcurrentQueue[T any](bufferSize int) *ConcurrentQueue[T] {
|
||||
return &ConcurrentQueue[T]{
|
||||
chanIn: make(chan T),
|
||||
chanOut: make(chan T, bufferSize),
|
||||
overflow: lru.NewList[T](),
|
||||
overflow: NewList[T](),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
+102
-13
@@ -2,47 +2,136 @@ package fn
|
||||
|
||||
// Either is a type that can be either left or right.
|
||||
type Either[L any, R any] struct {
|
||||
left Option[L]
|
||||
right Option[R]
|
||||
isRight bool
|
||||
left L
|
||||
right R
|
||||
}
|
||||
|
||||
// NewLeft returns an Either with a left value.
|
||||
func NewLeft[L any, R any](l L) Either[L, R] {
|
||||
return Either[L, R]{left: Some(l), right: None[R]()}
|
||||
return Either[L, R]{left: l}
|
||||
}
|
||||
|
||||
// NewRight returns an Either with a right value.
|
||||
func NewRight[L any, R any](r R) Either[L, R] {
|
||||
return Either[L, R]{left: None[L](), right: Some(r)}
|
||||
return Either[L, R]{isRight: true, right: r}
|
||||
}
|
||||
|
||||
// ElimEither is the universal Either eliminator. It can be used to safely
|
||||
// handle all possible values inside the Either by supplying two continuations,
|
||||
// one for each side of the Either.
|
||||
func ElimEither[L, R, O any](f func(L) O, g func(R) O, e Either[L, R]) O {
|
||||
if !e.isRight {
|
||||
return f(e.left)
|
||||
}
|
||||
|
||||
return g(e.right)
|
||||
}
|
||||
|
||||
// WhenLeft executes the given function if the Either is left.
|
||||
func (e Either[L, R]) WhenLeft(f func(L)) {
|
||||
e.left.WhenSome(f)
|
||||
if !e.isRight {
|
||||
f(e.left)
|
||||
}
|
||||
}
|
||||
|
||||
// WhenRight executes the given function if the Either is right.
|
||||
func (e Either[L, R]) WhenRight(f func(R)) {
|
||||
e.right.WhenSome(f)
|
||||
if e.isRight {
|
||||
f(e.right)
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeft returns true if the Either is left.
|
||||
func (e Either[L, R]) IsLeft() bool {
|
||||
return e.left.IsSome()
|
||||
return !e.isRight
|
||||
}
|
||||
|
||||
// IsRight returns true if the Either is right.
|
||||
func (e Either[L, R]) IsRight() bool {
|
||||
return e.right.IsSome()
|
||||
return e.isRight
|
||||
}
|
||||
|
||||
// LeftToOption converts a Left value to an Option, returning None if the inner
|
||||
// Either value is a Right value.
|
||||
func (e Either[L, R]) LeftToOption() Option[L] {
|
||||
if e.isRight {
|
||||
return None[L]()
|
||||
}
|
||||
|
||||
return Some(e.left)
|
||||
}
|
||||
|
||||
// RightToOption converts a Right value to an Option, returning None if the
|
||||
// inner Either value is a Left value.
|
||||
func (e Either[L, R]) RightToOption() Option[R] {
|
||||
if !e.isRight {
|
||||
return None[R]()
|
||||
}
|
||||
|
||||
return Some(e.right)
|
||||
}
|
||||
|
||||
// UnwrapLeftOr will extract the Left value from the Either if it is present
|
||||
// returning the supplied default if it is not.
|
||||
func (e Either[L, R]) UnwrapLeftOr(l L) L {
|
||||
if e.isRight {
|
||||
return l
|
||||
}
|
||||
|
||||
return e.left
|
||||
}
|
||||
|
||||
// UnwrapRightOr will extract the Right value from the Either if it is present
|
||||
// returning the supplied default if it is not.
|
||||
func (e Either[L, R]) UnwrapRightOr(r R) R {
|
||||
if !e.isRight {
|
||||
return r
|
||||
}
|
||||
|
||||
return e.right
|
||||
}
|
||||
|
||||
// Swap reverses the type argument order. This can be useful as an adapter
|
||||
// between APIs.
|
||||
func (e Either[L, R]) Swap() Either[R, L] {
|
||||
return Either[R, L]{
|
||||
isRight: !e.isRight,
|
||||
left: e.right,
|
||||
right: e.left,
|
||||
}
|
||||
}
|
||||
|
||||
// MapLeft maps the left value of the Either to a new value.
|
||||
func MapLeft[L any, R any, O any](f func(L) O) func(Either[L, R]) Option[O] {
|
||||
return func(e Either[L, R]) Option[O] {
|
||||
if e.IsLeft() {
|
||||
return MapOption(f)(e.left)
|
||||
func MapLeft[L, R, O any](f func(L) O) func(Either[L, R]) Either[O, R] {
|
||||
return func(e Either[L, R]) Either[O, R] {
|
||||
if !e.isRight {
|
||||
return Either[O, R]{
|
||||
isRight: false,
|
||||
left: f(e.left),
|
||||
}
|
||||
}
|
||||
|
||||
return None[O]()
|
||||
return Either[O, R]{
|
||||
isRight: true,
|
||||
right: e.right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MapRight maps the right value of the Either to a new value.
|
||||
func MapRight[L, R, O any](f func(R) O) func(Either[L, R]) Either[L, O] {
|
||||
return func(e Either[L, R]) Either[L, O] {
|
||||
if e.isRight {
|
||||
return Either[L, O]{
|
||||
isRight: true,
|
||||
right: f(e.right),
|
||||
}
|
||||
}
|
||||
|
||||
return Either[L, O]{
|
||||
isRight: false,
|
||||
left: e.left,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package fn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func TestPropConstructorEliminatorDuality(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
Len := func(s string) int { return len(s) } // smh
|
||||
if isRight {
|
||||
v := ElimEither(
|
||||
Iden[int],
|
||||
Len,
|
||||
NewRight[int, string](s),
|
||||
)
|
||||
return v == Len(s)
|
||||
}
|
||||
|
||||
v := ElimEither(
|
||||
Iden[int],
|
||||
Len,
|
||||
NewLeft[int, string](i),
|
||||
)
|
||||
return v == i
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropWhenClauseExclusivity(t *testing.T) {
|
||||
f := func(i int, isRight bool) bool {
|
||||
var e Either[int, int]
|
||||
if isRight {
|
||||
e = NewRight[int, int](i)
|
||||
} else {
|
||||
e = NewLeft[int, int](i)
|
||||
}
|
||||
z := 0
|
||||
e.WhenLeft(func(x int) { z += x })
|
||||
e.WhenRight(func(x int) { z += x })
|
||||
|
||||
return z != 2*i && e.IsLeft() != e.IsRight()
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropSwapEitherSelfInverting(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
var e Either[int, string]
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
}
|
||||
return e.Swap().Swap() == e
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropMapLeftIdentity(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
var e Either[int, string]
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
}
|
||||
return MapLeft[int, string, int](Iden[int])(e) == e
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropMapRightIdentity(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
var e Either[int, string]
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
}
|
||||
return MapRight[int, string, string](Iden[string])(e) == e
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropToOptionIdentities(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
var e Either[int, string]
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
|
||||
r2O := e.RightToOption() == Some(s)
|
||||
o2R := e == OptionToRight[string, int, string](
|
||||
Some(s), i,
|
||||
)
|
||||
l2O := e.LeftToOption() == None[int]()
|
||||
|
||||
return r2O && o2R && l2O
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
l2O := e.LeftToOption() == Some(i)
|
||||
o2L := e == OptionToLeft[int, int](Some(i), s)
|
||||
r2O := e.RightToOption() == None[string]()
|
||||
|
||||
return l2O && o2L && r2O
|
||||
}
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropUnwrapIdentities(t *testing.T) {
|
||||
f := func(i int, s string, isRight bool) bool {
|
||||
var e Either[int, string]
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
return e.UnwrapRightOr("") == s &&
|
||||
e.UnwrapLeftOr(0) == 0
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
return e.UnwrapLeftOr(0) == i &&
|
||||
e.UnwrapRightOr("") == ""
|
||||
}
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package fn
|
||||
|
||||
// Unit is a type alias for the empty struct to make it a bit less noisy to
|
||||
// communicate the informationaless type.
|
||||
type Unit = struct{}
|
||||
|
||||
// Comp is left to right function composition. Comp(f, g)(x) == g(f(x)). This
|
||||
// can make it easier to create on the fly closures that we may use as
|
||||
// arguments to other functions defined in this package (or otherwise).
|
||||
func Comp[A, B, C any](f func(A) B, g func(B) C) func(A) C {
|
||||
return func(a A) C {
|
||||
return g(f(a))
|
||||
}
|
||||
}
|
||||
|
||||
// Iden is the left and right identity of Comp. It is a function that simply
|
||||
// returns its argument. The utility of this function is only apparent in
|
||||
// conjunction with other functions in this package.
|
||||
func Iden[A any](a A) A {
|
||||
return a
|
||||
}
|
||||
|
||||
// Const is a function that accepts an argument and returns a function that
|
||||
// always returns that value irrespective of the returned function's argument.
|
||||
// This is also quite useful in conjunction with higher order functions.
|
||||
func Const[A, B any](a A) func(B) A {
|
||||
return func(_ B) A {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
// Eq is a curried function that returns true if its eventual two arguments are
|
||||
// equal.
|
||||
func Eq[A comparable](x A) func(A) bool {
|
||||
return func(y A) bool {
|
||||
return x == y
|
||||
}
|
||||
}
|
||||
|
||||
// Neq is a curried function that returns true if its eventual two arguments are
|
||||
// not equal.
|
||||
func Neq[A comparable](x A) func(A) bool {
|
||||
return func(y A) bool {
|
||||
return x != y
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package fn
|
||||
|
||||
// Reducer represents a function that takes an accumulator and the value, then
|
||||
// returns a new accumulator.
|
||||
type Reducer[T, V any] func(accum T, value V) T
|
||||
|
||||
// Reduce takes a slice of something, and a reducer, and produces a final
|
||||
// accumulated value.
|
||||
func Reduce[T any, V any, S []V](s S, f Reducer[T, V]) T {
|
||||
var accum T
|
||||
|
||||
for _, x := range s {
|
||||
accum = f(accum, x)
|
||||
}
|
||||
|
||||
return accum
|
||||
}
|
||||
@@ -3,9 +3,9 @@ module github.com/lightningnetwork/lnd/fn
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/lightninglabs/neutrino/cache v1.1.2
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
|
||||
golang.org/x/sync v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
|
||||
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
|
||||
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=
|
||||
@@ -14,6 +12,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
|
||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
// Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
// Copyright (c) 2024 Lightning Labs and the Lightning Network Developers
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package fn
|
||||
|
||||
type Node[A any] struct {
|
||||
// prev is a pointer to the previous node in the List.
|
||||
prev *Node[A]
|
||||
|
||||
// next is a pointer to the next node in the List.
|
||||
next *Node[A]
|
||||
|
||||
// list is the root pointer to the List in which this node is located.
|
||||
list *List[A]
|
||||
|
||||
// Value is the actual data contained within the Node.
|
||||
Value A
|
||||
}
|
||||
|
||||
// Next returns the next list node or nil.
|
||||
func (e *Node[A]) Next() *Node[A] {
|
||||
if e.list == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.next == &e.list.root {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e.next
|
||||
}
|
||||
|
||||
// Prev returns the previous list node or nil.
|
||||
func (e *Node[A]) Prev() *Node[A] {
|
||||
if e.list == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.prev == &e.list.root {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e.prev
|
||||
}
|
||||
|
||||
// List represents a doubly linked list.
|
||||
// The zero value for List is an empty list ready to use.
|
||||
type List[A any] struct {
|
||||
// root is a sentinal Node to identify the head and tail of the list.
|
||||
// root.prev is the tail, root.next is the head. For the purposes of
|
||||
// elegance, the absence of a next or prev node is encoded as the
|
||||
// address of the root node.
|
||||
root Node[A]
|
||||
|
||||
// len is the current length of the list.
|
||||
len int
|
||||
}
|
||||
|
||||
// Init intializes or clears the List l.
|
||||
func (l *List[A]) Init() *List[A] {
|
||||
l.root.next = &l.root
|
||||
l.root.prev = &l.root
|
||||
l.len = 0
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes a zero List value. It is called by other public
|
||||
// functions that could feasibly be called on a List that was initialized by the
|
||||
// raw List[A]{} constructor.
|
||||
func (l *List[A]) lazyInit() {
|
||||
if l.root.next == nil {
|
||||
l.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// insert inserts n after predecessor, increments l.len, and returns n.
|
||||
func (l *List[A]) insert(n *Node[A], predecessor *Node[A]) *Node[A] {
|
||||
// Make n point to correct neighborhood.
|
||||
n.prev = predecessor
|
||||
n.next = predecessor.next
|
||||
|
||||
// Make neighborhood point to n.
|
||||
n.prev.next = n
|
||||
n.next.prev = n
|
||||
|
||||
// Make n part of the list.
|
||||
n.list = l
|
||||
|
||||
// Increment list length.
|
||||
l.len++
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// insertVal is a convenience wrapper for
|
||||
// insert(&Node[A]{Value: v}, predecessor).
|
||||
func (l *List[A]) insertVal(a A, predecessor *Node[A]) *Node[A] {
|
||||
return l.insert(&Node[A]{Value: a}, predecessor)
|
||||
}
|
||||
|
||||
// move removes n from its current position and inserts it as the successor to
|
||||
// predecessor.
|
||||
func (l *List[A]) move(n *Node[A], predecessor *Node[A]) {
|
||||
if n == predecessor {
|
||||
return // Can't move after itself.
|
||||
}
|
||||
|
||||
if predecessor.next == n {
|
||||
return // Nothing to be done.
|
||||
}
|
||||
|
||||
// Bind previous and next to each other.
|
||||
n.prev.next = n.next
|
||||
n.next.prev = n.prev
|
||||
|
||||
// Make n point to new neighborhood.
|
||||
n.prev = predecessor
|
||||
n.next = predecessor.next
|
||||
|
||||
// Make new neighborhood point to n.
|
||||
n.prev.next = n
|
||||
n.next.prev = n
|
||||
}
|
||||
|
||||
// New returns an initialized List.
|
||||
func NewList[A any]() *List[A] {
|
||||
l := List[A]{}
|
||||
return l.Init()
|
||||
}
|
||||
|
||||
// Len returns the number of elements of List l.
|
||||
// The complexity is O(1).
|
||||
func (l *List[A]) Len() int {
|
||||
return l.len
|
||||
}
|
||||
|
||||
// Front returns the first Node of List l or nil if the list is empty.
|
||||
func (l *List[A]) Front() *Node[A] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.root.next
|
||||
}
|
||||
|
||||
// Back returns the last Node of List l or nil if the list is empty.
|
||||
func (l *List[A]) Back() *Node[A] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.root.prev
|
||||
}
|
||||
|
||||
// Remove removes Node n from List l if n is an element of List l.
|
||||
// It returns the Node value e.Value.
|
||||
// The Node must not be nil.
|
||||
func (l *List[A]) Remove(n *Node[A]) A {
|
||||
if n.list == l {
|
||||
n.prev.next = n.next
|
||||
n.next.prev = n.prev
|
||||
l.len--
|
||||
|
||||
v := n.Value
|
||||
// Set all node data to nil to prevent dangling references.
|
||||
*n = Node[A]{Value: v}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
return n.Value
|
||||
}
|
||||
|
||||
// PushFront inserts a new Node n with value a at the front of List l and
|
||||
// returns n.
|
||||
func (l *List[A]) PushFront(a A) *Node[A] {
|
||||
l.lazyInit()
|
||||
return l.insertVal(a, &l.root)
|
||||
}
|
||||
|
||||
// PushBack inserts a new Node n with value a at the back of List l and returns
|
||||
// n.
|
||||
func (l *List[A]) PushBack(a A) *Node[A] {
|
||||
l.lazyInit()
|
||||
return l.insertVal(a, l.root.prev)
|
||||
}
|
||||
|
||||
// InsertBefore inserts a new Node n with value a immediately before successor
|
||||
// and returns n. If successor is not an element of l, the list is not
|
||||
// modified. The successor must not be nil.
|
||||
func (l *List[A]) InsertBefore(a A, successor *Node[A]) *Node[A] {
|
||||
if successor == nil {
|
||||
return l.insertVal(a, &l.root)
|
||||
}
|
||||
|
||||
if successor.list != l {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.insertVal(a, successor.prev)
|
||||
}
|
||||
|
||||
// InsertAfter inserts a new Node n with value a immediately after and returns
|
||||
// e. If predecessor is not an element of l, the list is not modified. The
|
||||
// predecessor must not be nil.
|
||||
func (l *List[A]) InsertAfter(a A, predecessor *Node[A]) *Node[A] {
|
||||
if predecessor == nil {
|
||||
return l.insertVal(a, l.root.prev)
|
||||
}
|
||||
|
||||
if predecessor.list != l {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.insertVal(a, predecessor)
|
||||
}
|
||||
|
||||
// MoveToFront moves Node n to the front of List l.
|
||||
// If n is not an element of l, the list is not modified.
|
||||
// The Node must not be nil.
|
||||
func (l *List[A]) MoveToFront(n *Node[A]) {
|
||||
if n.list == l {
|
||||
l.move(n, &l.root)
|
||||
}
|
||||
}
|
||||
|
||||
// MoveToBack moves Node n to the back of List l.
|
||||
// If n is not an element of l, the list is not modified.
|
||||
// The Node must not be nil.
|
||||
func (l *List[A]) MoveToBack(n *Node[A]) {
|
||||
if n.list == l {
|
||||
l.move(n, l.root.prev)
|
||||
}
|
||||
}
|
||||
|
||||
// MoveBefore moves Node n to its new position before successor.
|
||||
// If n or successor is not an element of l, or n == successor, the list is not
|
||||
// modified. The Node and successor must not be nil.
|
||||
func (l *List[A]) MoveBefore(n, successor *Node[A]) {
|
||||
if n.list == l && successor.list == l {
|
||||
l.move(n, successor.prev)
|
||||
}
|
||||
}
|
||||
|
||||
// MoveAfter moves Node n to its new position after predecessor.
|
||||
// If n or predecessor is not an element of l, or n == predecessor, the list is
|
||||
// not modified. The Node and predecessor must not be nil.
|
||||
func (l *List[A]) MoveAfter(n, predecessor *Node[A]) {
|
||||
if n.list == l && predecessor.list == l {
|
||||
l.move(n, predecessor)
|
||||
}
|
||||
}
|
||||
|
||||
// PushBackList inserts a copy of List other at the back of List l.
|
||||
// The Lists l and other may be the same. They must not be nil.
|
||||
func (l *List[A]) PushBackList(other *List[A]) {
|
||||
l.lazyInit()
|
||||
n := other.Front()
|
||||
sz := other.Len()
|
||||
for i := 0; i < sz; i++ {
|
||||
l.insertVal(n.Value, l.root.prev)
|
||||
n = n.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// PushFrontList inserts a copy of List other at the front of List l.
|
||||
// The Lists l and other may be the same. They must not be nil.
|
||||
func (l *List[A]) PushFrontList(other *List[A]) {
|
||||
l.lazyInit()
|
||||
n := other.Back()
|
||||
sz := other.Len()
|
||||
for i := 0; i < sz; i++ {
|
||||
l.insertVal(n.Value, &l.root)
|
||||
n = n.Prev()
|
||||
}
|
||||
}
|
||||
+729
@@ -0,0 +1,729 @@
|
||||
package fn
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func GenList(r *rand.Rand) *List[uint32] {
|
||||
size := int(r.Uint32() >> 24)
|
||||
l := NewList[uint32]()
|
||||
for i := 0; i < size; i++ {
|
||||
l.PushBack(rand.Uint32())
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func GetRandNode(l *List[uint32], r *rand.Rand) *Node[uint32] {
|
||||
if l.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
idx := r.Uint32() % uint32(l.Len())
|
||||
n := l.Front()
|
||||
for i := 0; i < int(idx); i++ {
|
||||
n = n.Next()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func TestPushLenIncrement(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], x uint32, front bool) bool {
|
||||
sz := l.Len()
|
||||
if front {
|
||||
l.PushFront(x)
|
||||
} else {
|
||||
l.PushBack(x)
|
||||
}
|
||||
sz2 := l.Len()
|
||||
|
||||
return sz2 == sz+1
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
vs[0] = reflect.ValueOf(GenList(r))
|
||||
vs[1] = reflect.ValueOf(r.Uint32())
|
||||
vs[2] = reflect.ValueOf(r.Uint32()%2 == 0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveLenDecrement(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32]) bool {
|
||||
if l.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
sz := l.Len()
|
||||
l.Remove(n)
|
||||
sz2 := l.Len()
|
||||
|
||||
return sz2 == sz-1
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushGetCongruence(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], x uint32, front bool) bool {
|
||||
if front {
|
||||
l.PushFront(x)
|
||||
return l.Front().Value == x
|
||||
} else {
|
||||
l.PushBack(x)
|
||||
return l.Back().Value == x
|
||||
}
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
vs[0] = reflect.ValueOf(GenList(r))
|
||||
vs[1] = reflect.ValueOf(r.Uint32())
|
||||
vs[2] = reflect.ValueOf(r.Uint32()%2 == 0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertBeforeFrontIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], x uint32) bool {
|
||||
if l == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeX := l.InsertBefore(x, l.Front())
|
||||
|
||||
return nodeX == l.Front()
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(r.Uint32())
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAfterBackIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], x uint32) bool {
|
||||
if l == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeX := l.InsertAfter(x, l.Back())
|
||||
|
||||
return nodeX == l.Back()
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(r.Uint32())
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertBeforeNextIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32], x uint32) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeX := l.InsertBefore(x, n)
|
||||
return nodeX.Next() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
vs[2] = reflect.ValueOf(r.Uint32())
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertAfterPrevIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32], x uint32) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
nodeX := l.InsertAfter(x, n)
|
||||
return nodeX.Prev() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
vs[2] = reflect.ValueOf(r.Uint32())
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveToFrontFrontIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32]) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
l.MoveToFront(n)
|
||||
return l.Front() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveToBackBackIdentity(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32]) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
l.MoveToBack(n)
|
||||
return l.Back() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveBeforeFrontIsFront(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32]) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
l.MoveBefore(n, l.Front())
|
||||
return l.Front() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveAfterBackIsBack(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32]) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
l.MoveAfter(n, l.Back())
|
||||
return l.Back() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiMoveErasure(t *testing.T) {
|
||||
err := quick.Check(
|
||||
func(l *List[uint32], n *Node[uint32], m *Node[uint32]) bool {
|
||||
if n == nil || m == nil || n == m {
|
||||
return true
|
||||
}
|
||||
|
||||
l.MoveToFront(n)
|
||||
l.MoveToBack(n)
|
||||
l.MoveBefore(n, m)
|
||||
l.MoveAfter(n, m)
|
||||
return m.Next() == n
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(GetRandNode(l, r))
|
||||
vs[2] = reflect.ValueOf(GetRandNode(l, r))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushListSymmetry(t *testing.T) {
|
||||
copyList := func(l *List[uint32]) *List[uint32] {
|
||||
c := NewList[uint32]()
|
||||
for n := l.Front(); n != nil; n = n.Next() {
|
||||
c.PushBack(n.Value)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
err := quick.Check(
|
||||
func(l1 *List[uint32], l2 *List[uint32]) bool {
|
||||
if l1.Len() == 0 || l2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
l1Copy := copyList(l1)
|
||||
l2Copy := copyList(l2)
|
||||
|
||||
l1.PushBackList(l2Copy)
|
||||
l2.PushFrontList(l1Copy)
|
||||
|
||||
iter1 := l1.Front()
|
||||
iter2 := l2.Front()
|
||||
|
||||
for i := 0; i < l1Copy.Len()+l2Copy.Len()-1; i++ {
|
||||
if iter1.Value != iter2.Value {
|
||||
return false
|
||||
}
|
||||
|
||||
iter1 = iter1.Next()
|
||||
iter2 = iter2.Next()
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
&quick.Config{
|
||||
Values: func(vs []reflect.Value, r *rand.Rand) {
|
||||
l := GenList(r)
|
||||
l2 := GenList(r)
|
||||
vs[0] = reflect.ValueOf(l)
|
||||
vs[1] = reflect.ValueOf(l2)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue4103(t *testing.T) {
|
||||
l1 := NewList[int]()
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
|
||||
l2 := NewList[int]()
|
||||
l2.PushBack(3)
|
||||
l2.PushBack(4)
|
||||
|
||||
e := l1.Front()
|
||||
l2.Remove(e) // l2 should not change because e is not an element of l2
|
||||
if n := l2.Len(); n != 2 {
|
||||
t.Errorf("l2.Len() = %d, want 2", n)
|
||||
}
|
||||
|
||||
l1.InsertBefore(8, e)
|
||||
if n := l1.Len(); n != 3 {
|
||||
t.Errorf("l1.Len() = %d, want 3", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue6349(t *testing.T) {
|
||||
l := NewList[int]()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
|
||||
e := l.Front()
|
||||
l.Remove(e)
|
||||
if e.Value != 1 {
|
||||
t.Errorf("e.value = %d, want 1", e.Value)
|
||||
}
|
||||
if e.Next() != nil {
|
||||
t.Errorf("e.Next() != nil")
|
||||
}
|
||||
if e.Prev() != nil {
|
||||
t.Errorf("e.Prev() != nil")
|
||||
}
|
||||
}
|
||||
|
||||
func checkListLen[V any](t *testing.T, l *List[V], length int) bool {
|
||||
if n := l.Len(); n != length {
|
||||
t.Errorf("l.Len() = %d, want %d", n, length)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkListPointers[V any](t *testing.T, l *List[V], es []*Node[V]) {
|
||||
root := &l.root
|
||||
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
// zero length lists must be the zero value or properly initialized
|
||||
// (sentinel circle)
|
||||
if len(es) == 0 {
|
||||
if l.root.next != nil && l.root.next != root ||
|
||||
l.root.prev != nil && l.root.prev != root {
|
||||
|
||||
t.Errorf("l.root.next = %p, l.root.prev = %p;"+
|
||||
"both should both be nil or %p", l.root.next,
|
||||
l.root.prev, root)
|
||||
}
|
||||
return
|
||||
}
|
||||
// len(es) > 0
|
||||
|
||||
// check internal and external prev/next connections
|
||||
for i, e := range es {
|
||||
prev := root
|
||||
Prev := (*Node[V])(nil)
|
||||
if i > 0 {
|
||||
prev = es[i-1]
|
||||
Prev = prev
|
||||
}
|
||||
if p := e.prev; p != prev {
|
||||
t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p,
|
||||
prev)
|
||||
}
|
||||
if p := e.Prev(); p != Prev {
|
||||
t.Errorf("elt[%d](%p).Prev() = %p, want %p", i, e, p,
|
||||
Prev)
|
||||
}
|
||||
|
||||
next := root
|
||||
Next := (*Node[V])(nil)
|
||||
if i < len(es)-1 {
|
||||
next = es[i+1]
|
||||
Next = next
|
||||
}
|
||||
if n := e.next; n != next {
|
||||
t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n,
|
||||
next)
|
||||
}
|
||||
if n := e.Next(); n != Next {
|
||||
t.Errorf("elt[%d](%p).Next() = %p, want %p", i, e, n,
|
||||
Next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
l := NewList[int]()
|
||||
checkListPointers(t, l, []*Node[int]{})
|
||||
|
||||
// Single element list
|
||||
e := l.PushFront(5)
|
||||
checkListPointers(t, l, []*Node[int]{e})
|
||||
l.MoveToFront(e)
|
||||
checkListPointers(t, l, []*Node[int]{e})
|
||||
l.MoveToBack(e)
|
||||
checkListPointers(t, l, []*Node[int]{e})
|
||||
l.Remove(e)
|
||||
checkListPointers(t, l, []*Node[int]{})
|
||||
|
||||
// Bigger list
|
||||
e2 := l.PushFront(2)
|
||||
e1 := l.PushFront(1)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack(0)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
|
||||
|
||||
l.Remove(e2)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e3, e4})
|
||||
|
||||
l.MoveToFront(e3) // move from middle
|
||||
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
|
||||
|
||||
l.MoveToFront(e1)
|
||||
l.MoveToBack(e3) // move from middle
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
|
||||
|
||||
l.MoveToFront(e3) // move from back
|
||||
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
|
||||
l.MoveToFront(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Node[int]{e3, e1, e4})
|
||||
|
||||
l.MoveToBack(e3) // move from front
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
|
||||
l.MoveToBack(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e3})
|
||||
|
||||
e2 = l.InsertBefore(2, e1) // insert before front
|
||||
checkListPointers(t, l, []*Node[int]{e2, e1, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e4) // insert before middle
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e3) // insert before back
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
|
||||
e2 = l.InsertAfter(2, e1) // insert after front
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e4) // insert after middle
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e3) // insert after back
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e3, e2})
|
||||
l.Remove(e2)
|
||||
|
||||
// Check standard iteration.
|
||||
sum := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
sum += e.Value
|
||||
}
|
||||
if sum != 4 {
|
||||
t.Errorf("sum over l = %d, want 4", sum)
|
||||
}
|
||||
|
||||
// Clear all elements by iterating
|
||||
var next *Node[int]
|
||||
for e := l.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
l.Remove(e)
|
||||
}
|
||||
checkListPointers(t, l, []*Node[int]{})
|
||||
}
|
||||
|
||||
func checkList[V comparable](t *testing.T, l *List[V], es []V) {
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
le := e.Value
|
||||
if le != es[i] {
|
||||
t.Errorf("elt[%d].Value = %v, want %v", i, le, es[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtending(t *testing.T) {
|
||||
l1 := NewList[int]()
|
||||
l2 := NewList[int]()
|
||||
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
l1.PushBack(3)
|
||||
|
||||
l2.PushBack(4)
|
||||
l2.PushBack(5)
|
||||
|
||||
l3 := NewList[int]()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []int{1, 2, 3})
|
||||
l3.PushBackList(l2)
|
||||
checkList(t, l3, []int{1, 2, 3, 4, 5})
|
||||
|
||||
l3 = NewList[int]()
|
||||
l3.PushFrontList(l2)
|
||||
checkList(t, l3, []int{4, 5})
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []int{1, 2, 3, 4, 5})
|
||||
|
||||
checkList(t, l1, []int{1, 2, 3})
|
||||
checkList(t, l2, []int{4, 5})
|
||||
|
||||
l3 = NewList[int]()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []int{1, 2, 3})
|
||||
l3.PushBackList(l3)
|
||||
checkList(t, l3, []int{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = NewList[int]()
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []int{1, 2, 3})
|
||||
l3.PushFrontList(l3)
|
||||
checkList(t, l3, []int{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = NewList[int]()
|
||||
l1.PushBackList(l3)
|
||||
checkList(t, l1, []int{1, 2, 3})
|
||||
l1.PushFrontList(l3)
|
||||
checkList(t, l1, []int{1, 2, 3})
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
l := NewList[int]()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2})
|
||||
e := l.Front()
|
||||
l.Remove(e)
|
||||
checkListPointers(t, l, []*Node[int]{e2})
|
||||
l.Remove(e)
|
||||
checkListPointers(t, l, []*Node[int]{e2})
|
||||
}
|
||||
|
||||
func TestMove(t *testing.T) {
|
||||
l := NewList[int]()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack(4)
|
||||
|
||||
l.MoveAfter(e3, e3)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e2)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
|
||||
|
||||
l.MoveAfter(e3, e2)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e3)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e2, e3, e4})
|
||||
|
||||
l.MoveBefore(e2, e4)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e3, e2, e4})
|
||||
e2, e3 = e3, e2
|
||||
|
||||
l.MoveBefore(e4, e1)
|
||||
checkListPointers(t, l, []*Node[int]{e4, e1, e2, e3})
|
||||
e1, e2, e3, e4 = e4, e1, e2, e3
|
||||
|
||||
l.MoveAfter(e4, e1)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e4, e2, e3})
|
||||
e2, e3, e4 = e4, e2, e3
|
||||
|
||||
l.MoveAfter(e2, e3)
|
||||
checkListPointers(t, l, []*Node[int]{e1, e3, e2, e4})
|
||||
}
|
||||
|
||||
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
|
||||
func TestZeroList(t *testing.T) {
|
||||
var l1 = new(List[int])
|
||||
l1.PushFront(1)
|
||||
checkList(t, l1, []int{1})
|
||||
|
||||
var l2 = new(List[int])
|
||||
l2.PushBack(1)
|
||||
checkList(t, l2, []int{1})
|
||||
|
||||
var l3 = new(List[int])
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []int{1})
|
||||
|
||||
var l4 = new(List[int])
|
||||
l4.PushBackList(l2)
|
||||
checkList(t, l4, []int{1})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertBefore with a mark
|
||||
// that is not an element of l.
|
||||
func TestInsertBeforeUnknownMark(t *testing.T) {
|
||||
var l List[int]
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertBefore(1, new(Node[int]))
|
||||
checkList(t, &l, []int{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertAfter with a mark that
|
||||
// is not an element of l.
|
||||
func TestInsertAfterUnknownMark(t *testing.T) {
|
||||
var l List[int]
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertAfter(1, new(Node[int]))
|
||||
checkList(t, &l, []int{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling MoveAfter or MoveBefore with
|
||||
// a mark that is not an element of l.
|
||||
func TestMoveUnknownMark(t *testing.T) {
|
||||
var l1 List[int]
|
||||
e1 := l1.PushBack(1)
|
||||
|
||||
var l2 List[int]
|
||||
e2 := l2.PushBack(2)
|
||||
|
||||
l1.MoveAfter(e1, e2)
|
||||
checkList(t, &l1, []int{1})
|
||||
checkList(t, &l2, []int{2})
|
||||
|
||||
l1.MoveBefore(e1, e2)
|
||||
checkList(t, &l1, []int{1})
|
||||
checkList(t, &l2, []int{2})
|
||||
}
|
||||
@@ -213,3 +213,23 @@ func (o Option[A]) UnsafeFromSome() A {
|
||||
}
|
||||
panic("Option was None()")
|
||||
}
|
||||
|
||||
// OptionToLeft can be used to convert an Option value into an Either, by
|
||||
// providing the Right value that should be used if the Option value is None.
|
||||
func OptionToLeft[O, L, R any](o Option[O], r R) Either[O, R] {
|
||||
if o.IsSome() {
|
||||
return NewLeft[O, R](o.some)
|
||||
}
|
||||
|
||||
return NewRight[O, R](r)
|
||||
}
|
||||
|
||||
// OptionToRight can be used to convert an Option value into an Either, by
|
||||
// providing the Left value that should be used if the Option value is None.
|
||||
func OptionToRight[O, L, R any](o Option[O], l L) Either[L, O] {
|
||||
if o.IsSome() {
|
||||
return NewRight[L, O](o.some)
|
||||
}
|
||||
|
||||
return NewLeft[L, O](l)
|
||||
}
|
||||
+36
-24
@@ -39,12 +39,17 @@ func Errf[T any](errString string, args ...any) Result[T] {
|
||||
// Unpack extracts the value or error from the Result.
|
||||
func (r Result[T]) Unpack() (T, error) {
|
||||
var zero T
|
||||
return r.left.UnwrapOr(zero), r.right.UnwrapOr(nil)
|
||||
|
||||
if r.IsErr() {
|
||||
return zero, r.right
|
||||
}
|
||||
|
||||
return r.left, nil
|
||||
}
|
||||
|
||||
// Err exposes the underlying error of the result type as a normal error type.
|
||||
func (r Result[T]) Err() error {
|
||||
return r.right.some
|
||||
return r.right
|
||||
}
|
||||
|
||||
// IsOk returns true if the Result is a success value.
|
||||
@@ -59,66 +64,73 @@ func (r Result[T]) IsErr() bool {
|
||||
|
||||
// Map applies a function to the success value if it exists.
|
||||
func (r Result[T]) Map(f func(T) T) Result[T] {
|
||||
if r.IsOk() {
|
||||
return Ok(f(r.left.some))
|
||||
return Result[T]{
|
||||
MapLeft[T, error](f)(r.Either),
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// MapErr applies a function to the error value if it exists.
|
||||
func (r Result[T]) MapErr(f func(error) error) Result[T] {
|
||||
if r.IsErr() {
|
||||
return Err[T](f(r.right.some))
|
||||
return Result[T]{
|
||||
MapRight[T](f)(r.Either),
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Option returns the success value as an Option.
|
||||
func (r Result[T]) Option() Option[T] {
|
||||
return r.left
|
||||
return r.Either.LeftToOption()
|
||||
}
|
||||
|
||||
// WhenResult executes the given function if the Result is a success.
|
||||
func (r Result[T]) WhenResult(f func(T)) {
|
||||
r.left.WhenSome(func(t T) {
|
||||
f(t)
|
||||
})
|
||||
r.WhenLeft(f)
|
||||
}
|
||||
|
||||
// WhenErr executes the given function if the Result is an error.
|
||||
func (r Result[T]) WhenErr(f func(error)) {
|
||||
r.right.WhenSome(func(e error) {
|
||||
f(e)
|
||||
})
|
||||
r.WhenRight(f)
|
||||
}
|
||||
|
||||
// UnwrapOr returns the success value or a default value if it's an error.
|
||||
func (r Result[T]) UnwrapOr(defaultValue T) T {
|
||||
return r.left.UnwrapOr(defaultValue)
|
||||
if r.IsErr() {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return r.left
|
||||
}
|
||||
|
||||
// UnwrapOrElse returns the success value or computes a value from a function
|
||||
// if it's an error.
|
||||
func (r Result[T]) UnwrapOrElse(f func() T) T {
|
||||
return r.left.UnwrapOrFunc(f)
|
||||
if r.IsErr() {
|
||||
return f()
|
||||
}
|
||||
|
||||
return r.left
|
||||
}
|
||||
|
||||
// UnwrapOrFail returns the success value or fails the test if it's an error.
|
||||
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
|
||||
t.Helper()
|
||||
|
||||
return r.left.UnwrapOrFail(t)
|
||||
if r.IsErr() {
|
||||
t.Fatalf("Result[%T] contained error: %v", r.left, r.right)
|
||||
}
|
||||
|
||||
var zero T
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
// FlatMap applies a function that returns a Result to the success value if it
|
||||
// exists.
|
||||
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
|
||||
if r.IsOk() {
|
||||
return f(r.left.some)
|
||||
return r
|
||||
}
|
||||
return r
|
||||
|
||||
return f(r.left)
|
||||
}
|
||||
|
||||
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
||||
@@ -143,10 +155,10 @@ func (r Result[T]) OrElse(f func() Result[T]) Result[T] {
|
||||
// it exists.
|
||||
func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||
if r.IsOk() {
|
||||
return f(r.left.some)
|
||||
return f(r.left)
|
||||
}
|
||||
|
||||
return Err[B](r.right.some)
|
||||
return Err[B](r.right)
|
||||
}
|
||||
|
||||
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
||||
|
||||
+56
-3
@@ -1,6 +1,13 @@
|
||||
package fn
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
// Number is a type constraint for all numeric types in Go (integers,
|
||||
// float and complex numbers)
|
||||
@@ -46,7 +53,7 @@ func Map[A, B any](f func(A) B, s []A) []B {
|
||||
|
||||
// Filter creates a new slice of values where all the members of the returned
|
||||
// slice pass the predicate that is supplied in the argument.
|
||||
func Filter[A any](pred func(A) bool, s []A) []A {
|
||||
func Filter[A any](pred Pred[A], s []A) []A {
|
||||
res := make([]A, 0)
|
||||
|
||||
for _, val := range s {
|
||||
@@ -85,7 +92,7 @@ func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
|
||||
|
||||
// Find returns the first value that passes the supplied predicate, or None if
|
||||
// the value wasn't found.
|
||||
func Find[A any](pred func(A) bool, s []A) Option[A] {
|
||||
func Find[A any](pred Pred[A], s []A) Option[A] {
|
||||
for _, val := range s {
|
||||
if pred(val) {
|
||||
return Some(val)
|
||||
@@ -95,6 +102,23 @@ func Find[A any](pred func(A) bool, s []A) Option[A] {
|
||||
return None[A]()
|
||||
}
|
||||
|
||||
// FindIdx returns the first value that passes the supplied predicate along with
|
||||
// its index in the slice. If no satisfactory value is found, None is returned.
|
||||
func FindIdx[A any](pred Pred[A], s []A) Option[T2[int, A]] {
|
||||
for i, val := range s {
|
||||
if pred(val) {
|
||||
return Some(NewT2[int, A](i, val))
|
||||
}
|
||||
}
|
||||
|
||||
return None[T2[int, A]]()
|
||||
}
|
||||
|
||||
// Elem returns true if the element in the argument is found in the slice
|
||||
func Elem[A comparable](a A, s []A) bool {
|
||||
return Any(Eq(a), s)
|
||||
}
|
||||
|
||||
// Flatten takes a slice of slices and returns a concatenation of those slices.
|
||||
func Flatten[A any](s [][]A) []A {
|
||||
sz := Foldr(
|
||||
@@ -205,3 +229,32 @@ func Sum[B Number](items []B) B {
|
||||
func HasDuplicates[A comparable](items []A) bool {
|
||||
return len(NewSet(items...)) != len(items)
|
||||
}
|
||||
|
||||
// ForEachConc maps the argument function over the slice, spawning a new
|
||||
// goroutine for each element in the slice and then awaits all results before
|
||||
// returning them.
|
||||
func ForEachConc[A, B any](f func(A) B,
|
||||
as []A) []B {
|
||||
|
||||
var wait sync.WaitGroup
|
||||
ctx := context.Background()
|
||||
|
||||
sem := semaphore.NewWeighted(int64(runtime.NumCPU()))
|
||||
|
||||
bs := make([]B, len(as))
|
||||
|
||||
for i, a := range as {
|
||||
i, a := i, a
|
||||
sem.Acquire(ctx, 1)
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
bs[i] = f(a)
|
||||
wait.Done()
|
||||
sem.Release(1)
|
||||
}()
|
||||
}
|
||||
|
||||
wait.Wait()
|
||||
|
||||
return bs
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -281,3 +283,94 @@ func TestHasDuplicates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPropForEachConcMapIsomorphism ensures the property that ForEachConc and
|
||||
// Map always yield the same results.
|
||||
func TestPropForEachConcMapIsomorphism(t *testing.T) {
|
||||
f := func(incSize int, s []int) bool {
|
||||
inc := func(i int) int { return i + incSize }
|
||||
mapped := Map(inc, s)
|
||||
conc := ForEachConc(inc, s)
|
||||
|
||||
return slices.Equal(mapped, conc)
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPropForEachConcOutperformsMapWhenExpensive ensures the property that
|
||||
// ForEachConc will beat Map in a race in circumstances where the computation in
|
||||
// the argument closure is expensive.
|
||||
func TestPropForEachConcOutperformsMapWhenExpensive(t *testing.T) {
|
||||
f := func(incSize int, s []int) bool {
|
||||
if len(s) < 2 {
|
||||
// Intuitively we don't expect the extra overhead of
|
||||
// ForEachConc to justify itself for list sizes of 1 or
|
||||
// 0.
|
||||
return true
|
||||
}
|
||||
|
||||
inc := func(i int) int {
|
||||
time.Sleep(time.Millisecond)
|
||||
return i + incSize
|
||||
}
|
||||
c := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
Map(inc, s)
|
||||
select {
|
||||
case c <- false:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ForEachConc(inc, s)
|
||||
select {
|
||||
case c <- true:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
return <-c
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropFindIdxFindIdentity(t *testing.T) {
|
||||
f := func(div, mod uint8, s []uint8) bool {
|
||||
if div == 0 || div == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
pred := func(i uint8) bool {
|
||||
return i%div == mod
|
||||
}
|
||||
|
||||
foundIdx := FindIdx(pred, s)
|
||||
|
||||
// onlyVal :: Option[T2[A, B]] -> Option[B]
|
||||
onlyVal := MapOption(func(t2 T2[int, uint8]) uint8 {
|
||||
return t2.Second()
|
||||
})
|
||||
|
||||
valuesEqual := Find(pred, s) == onlyVal(foundIdx)
|
||||
|
||||
idxGetsVal := ElimOption(
|
||||
foundIdx,
|
||||
func() bool { return true },
|
||||
func(t2 T2[int, uint8]) bool {
|
||||
return s[t2.First()] == t2.Second()
|
||||
})
|
||||
|
||||
return valuesEqual && idxGetsVal
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package fn
|
||||
|
||||
// T2 is the simplest 2-tuple type. It is useful for capturing ad hoc
|
||||
// type conjunctions in a single value that can be easily dot-chained.
|
||||
type T2[A, B any] struct {
|
||||
first A
|
||||
second B
|
||||
}
|
||||
|
||||
// NewT2 is the canonical constructor for a T2. We include it because the fields
|
||||
// themselves are unexported.
|
||||
func NewT2[A, B any](a A, b B) T2[A, B] {
|
||||
return T2[A, B]{
|
||||
first: a,
|
||||
second: b,
|
||||
}
|
||||
}
|
||||
|
||||
// First returns the first value in the T2.
|
||||
func (t2 T2[A, B]) First() A {
|
||||
return t2.first
|
||||
}
|
||||
|
||||
// Second returns the second value in the T2.
|
||||
func (t2 T2[A, B]) Second() B {
|
||||
return t2.second
|
||||
}
|
||||
|
||||
// Unpack ejects the 2-tuple's members into the multiple return values that
|
||||
// are customary in go idiom.
|
||||
func (t2 T2[A, B]) Unpack() (A, B) {
|
||||
return t2.first, t2.second
|
||||
}
|
||||
|
||||
// Pair takes two functions that share the same argument type and runs them
|
||||
// both and produces a 2-tuple of the results.
|
||||
func Pair[A, B, C any](f func(A) B, g func(A) C) func(A) T2[B, C] {
|
||||
return func(a A) T2[B, C] {
|
||||
return NewT2[B, C](f(a), g(a))
|
||||
}
|
||||
}
|
||||
|
||||
// MapFirst lifts the argument function into one that applies to the first
|
||||
// element of a 2-tuple.
|
||||
func MapFirst[A, B, C any](f func(A) B) func(T2[A, C]) T2[B, C] {
|
||||
return func(t2 T2[A, C]) T2[B, C] {
|
||||
return NewT2[B, C](f(t2.First()), t2.Second())
|
||||
}
|
||||
}
|
||||
|
||||
// MapSecond lifts the argument function into one that applies to the second
|
||||
// element of a 2-tuple.
|
||||
func MapSecond[A, B, C any](f func(A) B) func(T2[C, A]) T2[C, B] {
|
||||
return func(t2 T2[C, A]) T2[C, B] {
|
||||
return NewT2[C, B](t2.First(), f(t2.Second()))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user