From 2f17af7b5d9318630b516576540660746a052e0f Mon Sep 17 00:00:00 2001 From: Ruidy Date: Sat, 1 Jan 2022 18:47:04 -0400 Subject: [PATCH] Chain (#7) * doc: add docs to chan methods * feat: contains * feat: chain each * feat: every each * feat: find each * feat: chain min/max * feat: chain Some * refactor: chain tests Co-authored-by: Ruidy --- README.md | 2 +- chain.go | 77 +++++++++++++++++++++++++++++++++++-- chain_test.go | 103 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 164 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 99fbc59..a47cc44 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ go test ./... Calling `NewChain` will cause all future method calls to return wrapped objects. When you've finished the computation, call `Value` to retrieve the final value. -Methods not returning a collection such as `Reduce`, `Every`, `Some`, will break the chain and return `Value` instantly. +Methods not returning a slice such as `Reduce`, `Every`, `Some`, will break the chain and return `Value` instantly. ## Built With diff --git a/chain.go b/chain.go index 1a57244..ba68dd6 100644 --- a/chain.go +++ b/chain.go @@ -1,21 +1,90 @@ package underscore -type Chain[T any] struct { +import "constraints" + +type Chain[T constraints.Ordered] struct { Value []T } -func NewChain[T any](value []T) Chain[T] { +// NewChain starts a Chain. All future method calls will return Chain structs. When you've finished the computation, +// call Value to retrieve the final value. +// +// Methods not returning a slice such as Reduce, Every, Some, will break the chain and return Value instantly. +func NewChain[T constraints.Ordered](value []T) Chain[T] { return Chain[T]{Value: value} } +// Contains returns true if the value is present in the slice and breaks the Chain. +func (c Chain[T]) Contains(value T) bool { + return Contains(c.Value, value) +} + +// Each iterates over a slice of elements, yielding each in turn to an action function. +// Breaks the Chain. +func (c Chain[T]) Each(action func(T)) { + Each(c.Value, action) +} + +// Every returns true if all the values in the slice pass the predicate truth test. +// Short-circuits and stops traversing the slice if a false element is found. +// Breaks the Chain. +func (c Chain[T]) Every(predicate func(T) bool) bool { + return Every(c.Value, predicate) +} + +// Filter looks through each value in the slice, returning a slice of all the values that pass a truth test (predicate). func (c Chain[T]) Filter(predicate func(n T) bool) Chain[T] { return Chain[T]{Value: Filter(c.Value, predicate)} } +// Find looks through each value in the slice, returning the first one that passes a truth test (predicate), +// or the default value for the type and an error if no value passes the test. +// The function returns as soon as it finds an acceptable element, and doesn't traverse the entire slice. +// Breaks the Chain. +func (c Chain[T]) Find(predicate func(n T) bool) (T, error) { + return Find(c.Value, predicate) +} + +// Map produces a new slice of values by mapping each value in the slice through +// a transform function. +// +// TODO: Move from T to P. func (c Chain[T]) Map(transform func(n T) T) Chain[T] { return Chain[T]{Value: Map(c.Value, transform)} } -func (c Chain[T]) Reduce(reducer func(n, acc T) T, initialValue T) T { - return Reduce(c.Value, reducer, initialValue) +// Max returns the maximum value in the slice. +// This function can currently only compare numbers reliably. +// This function uses operator <. +// Breaks the Chain. +func (c Chain[T]) Max() T { + return Max(c.Value) +} + +// Min returns the minimum value in the slice. +// This function can currently only compare numbers reliably. +// This function uses operator <. +// Breaks the Chain. +func (c Chain[T]) Min() T { + return Min(c.Value) +} + +// Partition splits the slice into two slices: one whose elements all satisfy predicate +// and one whose elements all do not satisfy predicate. +// Breaks the Chain. +func (c Chain[T]) Partition(predicate func(T) bool) ([]T, []T) { + return Partition(c.Value, predicate) +} + +// Reduce combine a list of values into a single value and breaks the Chain. +// acc is the initial state, and each successive step of it should be returned by the reduction function. +func (c Chain[T]) Reduce(reducer func(n, acc T) T, acc T) T { + return Reduce(c.Value, reducer, acc) +} + +// Some returns true if any of the values in the slice pass the predicate truth test. +// Short-circuits and stops traversing the slice if a true element is found. +// Breaks the Chain. +func (c Chain[T]) Some(predicate func(T) bool) bool { + return Some(c.Value, predicate) } diff --git a/chain_test.go b/chain_test.go index c1e2e9f..b669314 100644 --- a/chain_test.go +++ b/chain_test.go @@ -8,26 +8,103 @@ import ( u "github.com/rjNemo/underscore" ) -func TestChain(t *testing.T) { - nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} - isEven := func(n int) bool { return n%2 == 0 } - toSquare := func(n int) int { return n * n } - sum := func(n, acc int) int { return n + acc } - +func TestChainFilter(t *testing.T) { want := []int{2, 4, 6, 8} - assert.Equal(t, want, u.NewChain(nums). - Filter(isEven). - Value) + assert.Equal(t, + want, + u.NewChain(nums).Filter(isEven).Value, + ) +} - want = []int{4, 16, 36, 64} +func TestChainFilterMap(t *testing.T) { + want := []int{4, 16, 36, 64} + assert.Equal(t, + want, + u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Value) +} + +func TestChainFilterMapReduce(t *testing.T) { + want := 120 + assert.Equal(t, + want, + u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Reduce(sum, 0)) +} + +func TestChainFilterMapContains(t *testing.T) { + assert.True(t, u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Contains(16)) +} + +func TestChainFilterMapEach(t *testing.T) { + want := []int{5, 17, 37, 65} + res := make([]int, 0) + u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Each(func(n int) { res = append(res, n+1) }) + assert.Equal(t, want, res) +} + +func TestChainFilterMapEvery(t *testing.T) { + assert.True(t, u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Every(func(n int) bool { return n%4 == 0 })) +} + +func TestChainFilterMapFind(t *testing.T) { + n, err := u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Find(func(n int) bool { return n%4 == 0 }) + assert.Equal(t, 4, n) + assert.NoError(t, err) +} + +func TestChainFilterMapMax(t *testing.T) { + want := 64 assert.Equal(t, want, u.NewChain(nums). Filter(isEven). Map(toSquare). - Value) + Max()) +} - w := 120 +func TestChainFilterMapMin(t *testing.T) { + w := 4 assert.Equal(t, w, u.NewChain(nums). Filter(isEven). Map(toSquare). - Reduce(sum, 0)) + Min()) } + +func TestChainFilterMapPartition(t *testing.T) { + wantLeft := []int{4, 16} + wantRight := []int{36, 64} + left, right := u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Partition(func(n int) bool { return n < 20 }) + + assert.Equal(t, wantLeft, left) + assert.Equal(t, wantRight, right) +} + +func TestChainFilterMapSome(t *testing.T) { + assert.True(t, u.NewChain(nums). + Filter(isEven). + Map(toSquare). + Some(func(n int) bool { return n%64 == 0 })) +} + +var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} +var isEven = func(n int) bool { return n%2 == 0 } +var toSquare = func(n int) int { return n * n } +var sum = func(n, acc int) int { return n + acc }