diff --git a/distinct.go b/distinct.go index 22c6f13..0aae9ef 100644 --- a/distinct.go +++ b/distinct.go @@ -7,12 +7,19 @@ var distinct = &typewriter.Template{ Text: ` // Distinct returns a new {{.SliceName}} whose elements are unique. See: http://clipperhouse.github.io/gen/#Distinct func (rcv {{.SliceName}}) Distinct() (result {{.SliceName}}) { - appended := make(map[{{.Type}}]bool) + appended := make(map[{{.Type.Name}}]bool) for _, v := range rcv { - if !appended[v] { - result = append(result, v) - appended[v] = true - } + {{ if .Type.Pointer -}} + if !appended[*v] { + result = append(result, v) + appended[*v] = true + } + {{ else -}} + if !appended[v] { + result = append(result, v) + appended[v] = true + } + {{ end -}} } return result } diff --git a/test/aggregate[t]_test.go b/test/aggregate[t]_test.go index 40a083b..3f1fefb 100644 --- a/test/aggregate[t]_test.go +++ b/test/aggregate[t]_test.go @@ -20,3 +20,22 @@ func TestAggregateOther(t *testing.T) { t.Errorf("AggregateOther should be %v, got %v", expected1, aggregate1) } } + +func TestPointerAggregateOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + sum := func(state Other, x *PointerThing) Other { + return state + x.Number + } + + aggregate1 := things.AggregateOther(sum) + expected1 := Other(140) + + if aggregate1 != expected1 { + t.Errorf("AggregateOther should be %v, got %v", expected1, aggregate1) + } +} diff --git a/test/all_test.go b/test/all_test.go index 677a0f9..cb88e16 100644 --- a/test/all_test.go +++ b/test/all_test.go @@ -33,3 +33,35 @@ func TestAll(t *testing.T) { t.Errorf("All should evaulate true for empty slices") } } + +func TestPointerAll(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + all1 := things.All(func(x *PointerThing) bool { + return x.Name == "First" + }) + + if all1 { + t.Errorf("All should be false for Name == 'First'") + } + + all2 := things.All(func(x *PointerThing) bool { + return x.Name == "First" || x.Name == "Second" || x.Name == "Third" + }) + + if !all2 { + t.Errorf("All should be true") + } + + all3 := PointerThingSlice{}.All(func(x *PointerThing) bool { + return false + }) + + if !all3 { + t.Errorf("All should evaulate true for empty slices") + } +} diff --git a/test/any_test.go b/test/any_test.go index aa07588..eb1f6ec 100644 --- a/test/any_test.go +++ b/test/any_test.go @@ -33,3 +33,35 @@ func TestAny(t *testing.T) { t.Errorf("Any should evaulate false for empty slices") } } + +func TestPointerAny(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + any1 := things.Any(func(x *PointerThing) bool { + return x.Name == "Dummy" + }) + + if any1 { + t.Errorf("Any should not evaulate true for Name == Dummy") + } + + any2 := things.Any(func(x *PointerThing) bool { + return x.Number > 50 + }) + + if !any2 { + t.Errorf("Any should evaulate true for Number > 50") + } + + any3 := PointerThingSlice{}.Any(func(x *PointerThing) bool { + return true + }) + + if any3 { + t.Errorf("Any should evaulate false for empty slices") + } +} diff --git a/test/average[t]_test.go b/test/average[t]_test.go index 624a30b..0217710 100644 --- a/test/average[t]_test.go +++ b/test/average[t]_test.go @@ -31,3 +31,33 @@ func TestAverageOther(t *testing.T) { t.Errorf("Average should fail on empty slice") } } + +func TestPointerAverageOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -10}, + {"Third", 100}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + average1, err := things.AverageOther(number) + + if err != nil { + t.Errorf("Average should succeed") + } + + expected1 := Other(50) + + if average1 != expected1 { + t.Errorf("Average should be %v, got %v", expected1, average1) + } + + average2, err := PointerThingSlice{}.AverageOther(number) + + if err == nil || average2 != 0 { + t.Errorf("Average should fail on empty slice") + } +} diff --git a/test/count_test.go b/test/count_test.go index a864062..b8a5439 100644 --- a/test/count_test.go +++ b/test/count_test.go @@ -41,3 +41,43 @@ func TestCount(t *testing.T) { t.Errorf("Count should find no items in an empty slice") } } + +func TestPointerCount(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", 20}, + {"Third", 100}, + } + + count1 := things.Count(func(x *PointerThing) bool { + return x.Name == "Second" + }) + + if count1 != 1 { + t.Errorf("Count should find one item Name == Second") + } + + count2 := things.Count(func(x *PointerThing) bool { + return x.Number > 50 + }) + + if count2 != 2 { + t.Errorf("Count should find 2 items for Number > 50") + } + + count3 := things.Count(func(x *PointerThing) bool { + return x.Name == "Dummy" + }) + + if count3 != 0 { + t.Errorf("Count should no items for Name == Dummy") + } + + count4 := PointerThingSlice{}.Count(func(x *PointerThing) bool { + return true + }) + + if count4 != 0 { + t.Errorf("Count should find no items in an empty slice") + } +} diff --git a/test/distinct_test.go b/test/distinct_test.go index 223dc69..46d0f49 100644 --- a/test/distinct_test.go +++ b/test/distinct_test.go @@ -25,3 +25,24 @@ func TestDistinct(t *testing.T) { t.Errorf("Distinct should exclude be %v, but found %v", should, distinct1) } } + +func TestPointerDistinct(t *testing.T) { + things := PointerThingSlice{ + {"First", 0}, + {"Second", 0}, + {"First", 0}, + {"Third", 0}, + } + + should := PointerThingSlice{ + {"First", 0}, + {"Second", 0}, + {"Third", 0}, + } + + distinct1 := things.Distinct() + + if !reflect.DeepEqual(distinct1, should) { + t.Errorf("Distinct should exclude be %v, but found %v", should, distinct1) + } +} diff --git a/test/distinctby_test.go b/test/distinctby_test.go index 53d52f5..d85a54e 100644 --- a/test/distinctby_test.go +++ b/test/distinctby_test.go @@ -30,3 +30,29 @@ func TestDistinctBy(t *testing.T) { t.Errorf("DistinctBy should be %v, but got %v", expected, distinctby1) } } + +func TestPointerDistinctBy(t *testing.T) { + things := PointerThingSlice{ + {"First", 0}, + {"Second", 9}, + {"First", 4}, + {"Third", 9}, + {"Fourth", 5}, + {"Fifth", 4}, + } + + expected := PointerThingSlice{ + {"First", 0}, + {"Second", 9}, + {"First", 4}, + {"Fourth", 5}, + } + + distinctby1 := things.DistinctBy(func(a, b *PointerThing) bool { + return a.Number == b.Number + }) + + if !reflect.DeepEqual(distinctby1, expected) { + t.Errorf("DistinctBy should be %v, but got %v", expected, distinctby1) + } +} diff --git a/test/first_test.go b/test/first_test.go index fb0857c..0f642ba 100644 --- a/test/first_test.go +++ b/test/first_test.go @@ -38,3 +38,40 @@ func TestFirst(t *testing.T) { t.Errorf("First should fail on empty slice") } } + +func TestPointerFirst(t *testing.T) { + things := PointerThingSlice{ + {"First", 0}, + {"Second", 0}, + {"Third", 0}, + } + + f1, err1 := things.First(func(x *PointerThing) bool { + return x.Name == "Third" + }) + + if err1 != nil { + t.Errorf("First should succeed when finding Name == Third") + } + + expected1 := &PointerThing{"Third", 0} + if *f1 != *expected1 { + t.Errorf("First should find %v, but found %v", expected1, f1) + } + + _, err2 := things.First(func(x *PointerThing) bool { + return x.Name == "Dummy" + }) + + if err2 == nil { + t.Errorf("First should fail when finding Name == Dummy") + } + + _, err3 := PointerThingSlice{}.First(func(x *PointerThing) bool { + return true + }) + + if err3 == nil { + t.Errorf("First should fail on empty slice") + } +} diff --git a/test/groupby[t]_test.go b/test/groupby[t]_test.go index 4898c1b..7cee052 100644 --- a/test/groupby[t]_test.go +++ b/test/groupby[t]_test.go @@ -33,3 +33,32 @@ func TestGroupByOther(t *testing.T) { t.Errorf("GroupByOther should be %v, got %v", expected1, groupby1) } } + +func TestPointerGroupByOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -10}, + {"Third", 100}, + {"Fourth", -10}, + {"Fifth", 60}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + groupby1 := things.GroupByOther(number) + expected1 := map[Other]PointerThingSlice{ + -10: {{"Second", -10}, {"Fourth", -10}}, + 60: {{"First", 60}, {"Fifth", 60}}, + 100: {{"Third", 100}}, + } + + if len(groupby1) != len(expected1) { + t.Errorf("GroupByInt result should have %d elements, has %d", len(expected1), len(groupby1)) + } + + if !reflect.DeepEqual(groupby1, expected1) { + t.Errorf("GroupByOther should be %v, got %v", expected1, groupby1) + } +} diff --git a/test/max[t]_test.go b/test/max[t]_test.go index 5c7bc97..ed14313 100644 --- a/test/max[t]_test.go +++ b/test/max[t]_test.go @@ -29,3 +29,31 @@ func TestMaxOther(t *testing.T) { t.Errorf("Max should fail on empty slice") } } + +func TestPointerMaxOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + max1, err := things.MaxOther(number) + + if err != nil { + t.Errorf("MaxOther should succeed") + } + + if max1 != 100 { + t.Errorf("MaxOther should be %v, got %v", 100, max1) + } + + max2, err := PointerThingSlice{}.MaxOther(number) + + if err == nil || max2 != 0 { + t.Errorf("Max should fail on empty slice") + } +} diff --git a/test/maxby_test.go b/test/maxby_test.go index b8e910a..638712f 100644 --- a/test/maxby_test.go +++ b/test/maxby_test.go @@ -32,3 +32,34 @@ func TestMaxBy(t *testing.T) { t.Errorf("MaxBy Number should fail on empty slice") } } + +func TestPointerMaxBy(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + {"Fourth", 10}, + } + + max1, err1 := things.MaxBy(func(a, b *PointerThing) bool { + return a.Number < b.Number + }) + + if err1 != nil { + t.Errorf("MaxBy Number should succeed") + } + + expected1 := &PointerThing{"Third", 100} + + if *max1 != *expected1 { + t.Errorf("MaxBy Number should return %v, got %v", expected1, max1) + } + + _, err2 := PointerThingSlice{}.MaxBy(func(a, b *PointerThing) bool { + return true + }) + + if err2 == nil { + t.Errorf("MaxBy Number should fail on empty slice") + } +} diff --git a/test/min[t]_test.go b/test/min[t]_test.go index 1634f71..464ac89 100644 --- a/test/min[t]_test.go +++ b/test/min[t]_test.go @@ -29,3 +29,31 @@ func TestMinOther(t *testing.T) { t.Errorf("MinOther should fail on empty slice") } } + +func TestPointerMinOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + min1, err := things.MinOther(number) + + if err != nil { + t.Errorf("MinOther should succeed") + } + + if min1 != -20 { + t.Errorf("MinOther should be %v, got %v", -20, min1) + } + + min2, err := PointerThingSlice{}.MinOther(number) + + if err == nil || min2 != 0 { + t.Errorf("MinOther should fail on empty slice") + } +} diff --git a/test/minby_test.go b/test/minby_test.go index b0e7125..7c5d4ec 100644 --- a/test/minby_test.go +++ b/test/minby_test.go @@ -30,3 +30,32 @@ func TestMinBy(t *testing.T) { t.Errorf("MinBy Number should fail on empty slice") } } + +func TestPointerMinBy(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + min1, err1 := things.MinBy(func(a, b *PointerThing) bool { + return a.Number < b.Number + }) + + if err1 != nil { + t.Errorf("MinBy Number should succeed") + } + + expected1 := &PointerThing{"Second", -20} + if *min1 != *expected1 { + t.Errorf("MinBy Number should return %v, got %v", expected1, min1) + } + + _, err2 := PointerThingSlice{}.MinBy(func(a, b *PointerThing) bool { + return true + }) + + if err2 == nil { + t.Errorf("MinBy Number should fail on empty slice") + } +} diff --git a/test/pointerthing_slice.go b/test/pointerthing_slice.go new file mode 100644 index 0000000..6b03caa --- /dev/null +++ b/test/pointerthing_slice.go @@ -0,0 +1,472 @@ +// Generated by: setup +// TypeWriter: slice +// Directive: +test on *PointerThing + +package main + +import ( + "errors" + "math/rand" +) + +// Sort implementation is a modification of http://golang.org/pkg/sort/#Sort +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found at http://golang.org/LICENSE. + +// PointerThingSlice is a slice of type *PointerThing. Use it where you would use []*PointerThing. +type PointerThingSlice []*PointerThing + +// Any verifies that one or more elements of PointerThingSlice return true for the passed func. See: http://clipperhouse.github.io/gen/#Any +func (rcv PointerThingSlice) Any(fn func(*PointerThing) bool) bool { + for _, v := range rcv { + if fn(v) { + return true + } + } + return false +} + +// All verifies that all elements of PointerThingSlice return true for the passed func. See: http://clipperhouse.github.io/gen/#All +func (rcv PointerThingSlice) All(fn func(*PointerThing) bool) bool { + for _, v := range rcv { + if !fn(v) { + return false + } + } + return true +} + +// Count gives the number elements of PointerThingSlice that return true for the passed func. See: http://clipperhouse.github.io/gen/#Count +func (rcv PointerThingSlice) Count(fn func(*PointerThing) bool) (result int) { + for _, v := range rcv { + if fn(v) { + result++ + } + } + return +} + +// Distinct returns a new PointerThingSlice whose elements are unique. See: http://clipperhouse.github.io/gen/#Distinct +func (rcv PointerThingSlice) Distinct() (result PointerThingSlice) { + appended := make(map[PointerThing]bool) + for _, v := range rcv { + if !appended[*v] { + result = append(result, v) + appended[*v] = true + } + } + return result +} + +// DistinctBy returns a new PointerThingSlice whose elements are unique, where equality is defined by a passed func. See: http://clipperhouse.github.io/gen/#DistinctBy +func (rcv PointerThingSlice) DistinctBy(equal func(*PointerThing, *PointerThing) bool) (result PointerThingSlice) { +Outer: + for _, v := range rcv { + for _, r := range result { + if equal(v, r) { + continue Outer + } + } + result = append(result, v) + } + return result +} + +// Each iterates over PointerThingSlice and executes the passed func against each element. See: http://clipperhouse.github.io/gen/#Each +func (rcv PointerThingSlice) Each(fn func(*PointerThing)) { + for _, v := range rcv { + fn(v) + } +} + +// First returns the first element that returns true for the passed func. Returns error if no elements return true. See: http://clipperhouse.github.io/gen/#First +func (rcv PointerThingSlice) First(fn func(*PointerThing) bool) (result *PointerThing, err error) { + for _, v := range rcv { + if fn(v) { + result = v + return + } + } + err = errors.New("no PointerThingSlice elements return true for passed func") + return +} + +// MaxBy returns an element of PointerThingSlice containing the maximum value, when compared to other elements using a passed func defining ‘less’. In the case of multiple items being equally maximal, the last such element is returned. Returns error if no elements. See: http://clipperhouse.github.io/gen/#MaxBy +func (rcv PointerThingSlice) MaxBy(less func(*PointerThing, *PointerThing) bool) (result *PointerThing, err error) { + l := len(rcv) + if l == 0 { + err = errors.New("cannot determine the MaxBy of an empty slice") + return + } + m := 0 + for i := 1; i < l; i++ { + if rcv[i] != rcv[m] && !less(rcv[i], rcv[m]) { + m = i + } + } + result = rcv[m] + return +} + +// MinBy returns an element of PointerThingSlice containing the minimum value, when compared to other elements using a passed func defining ‘less’. In the case of multiple items being equally minimal, the first such element is returned. Returns error if no elements. See: http://clipperhouse.github.io/gen/#MinBy +func (rcv PointerThingSlice) MinBy(less func(*PointerThing, *PointerThing) bool) (result *PointerThing, err error) { + l := len(rcv) + if l == 0 { + err = errors.New("cannot determine the Min of an empty slice") + return + } + m := 0 + for i := 1; i < l; i++ { + if less(rcv[i], rcv[m]) { + m = i + } + } + result = rcv[m] + return +} + +// Single returns exactly one element of PointerThingSlice that returns true for the passed func. Returns error if no or multiple elements return true. See: http://clipperhouse.github.io/gen/#Single +func (rcv PointerThingSlice) Single(fn func(*PointerThing) bool) (result *PointerThing, err error) { + var candidate *PointerThing + found := false + for _, v := range rcv { + if fn(v) { + if found { + err = errors.New("multiple PointerThingSlice elements return true for passed func") + return + } + candidate = v + found = true + } + } + if found { + result = candidate + } else { + err = errors.New("no PointerThingSlice elements return true for passed func") + } + return +} + +// Where returns a new PointerThingSlice whose elements return true for func. See: http://clipperhouse.github.io/gen/#Where +func (rcv PointerThingSlice) Where(fn func(*PointerThing) bool) (result PointerThingSlice) { + for _, v := range rcv { + if fn(v) { + result = append(result, v) + } + } + return result +} + +// SortBy returns a new ordered PointerThingSlice, determined by a func defining ‘less’. See: http://clipperhouse.github.io/gen/#SortBy +func (rcv PointerThingSlice) SortBy(less func(*PointerThing, *PointerThing) bool) PointerThingSlice { + result := make(PointerThingSlice, len(rcv)) + copy(result, rcv) + // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached. + n := len(result) + maxDepth := 0 + for i := n; i > 0; i >>= 1 { + maxDepth++ + } + maxDepth *= 2 + quickSortPointerThingSlice(result, less, 0, n, maxDepth) + return result +} + +// SortByDesc returns a new, descending-ordered PointerThingSlice, determined by a func defining ‘less’. See: http://clipperhouse.github.io/gen/#SortBy +func (rcv PointerThingSlice) SortByDesc(less func(*PointerThing, *PointerThing) bool) PointerThingSlice { + greater := func(a, b *PointerThing) bool { + return less(b, a) + } + return rcv.SortBy(greater) +} + +// IsSortedBy reports whether an instance of PointerThingSlice is sorted, using the pass func to define ‘less’. See: http://clipperhouse.github.io/gen/#SortBy +func (rcv PointerThingSlice) IsSortedBy(less func(*PointerThing, *PointerThing) bool) bool { + n := len(rcv) + for i := n - 1; i > 0; i-- { + if less(rcv[i], rcv[i-1]) { + return false + } + } + return true +} + +// IsSortedDesc reports whether an instance of PointerThingSlice is sorted in descending order, using the pass func to define ‘less’. See: http://clipperhouse.github.io/gen/#SortBy +func (rcv PointerThingSlice) IsSortedByDesc(less func(*PointerThing, *PointerThing) bool) bool { + greater := func(a, b *PointerThing) bool { + return less(b, a) + } + return rcv.IsSortedBy(greater) +} + +// AggregateOther iterates over PointerThingSlice, operating on each element while maintaining ‘state’. See: http://clipperhouse.github.io/gen/#Aggregate +func (rcv PointerThingSlice) AggregateOther(fn func(Other, *PointerThing) Other) (result Other) { + for _, v := range rcv { + result = fn(result, v) + } + return +} + +// AverageOther sums Other over all elements and divides by len(PointerThingSlice). See: http://clipperhouse.github.io/gen/#Average +func (rcv PointerThingSlice) AverageOther(fn func(*PointerThing) Other) (result Other, err error) { + l := len(rcv) + if l == 0 { + err = errors.New("cannot determine Average[Other] of zero-length PointerThingSlice") + return + } + for _, v := range rcv { + result += fn(v) + } + result = result / Other(l) + return +} + +// GroupByOther groups elements into a map keyed by Other. See: http://clipperhouse.github.io/gen/#GroupBy +func (rcv PointerThingSlice) GroupByOther(fn func(*PointerThing) Other) map[Other]PointerThingSlice { + result := make(map[Other]PointerThingSlice) + for _, v := range rcv { + key := fn(v) + result[key] = append(result[key], v) + } + return result +} + +// MaxOther selects the largest value of Other in PointerThingSlice. Returns error on PointerThingSlice with no elements. See: http://clipperhouse.github.io/gen/#MaxCustom +func (rcv PointerThingSlice) MaxOther(fn func(*PointerThing) Other) (result Other, err error) { + l := len(rcv) + if l == 0 { + err = errors.New("cannot determine Max of zero-length PointerThingSlice") + return + } + result = fn(rcv[0]) + if l > 1 { + for _, v := range rcv[1:] { + f := fn(v) + if f > result { + result = f + } + } + } + return +} + +// MinOther selects the least value of Other in PointerThingSlice. Returns error on PointerThingSlice with no elements. See: http://clipperhouse.github.io/gen/#MinCustom +func (rcv PointerThingSlice) MinOther(fn func(*PointerThing) Other) (result Other, err error) { + l := len(rcv) + if l == 0 { + err = errors.New("cannot determine Min of zero-length PointerThingSlice") + return + } + result = fn(rcv[0]) + if l > 1 { + for _, v := range rcv[1:] { + f := fn(v) + if f < result { + result = f + } + } + } + return +} + +// SelectOther projects a slice of Other from PointerThingSlice, typically called a map in other frameworks. See: http://clipperhouse.github.io/gen/#Select +func (rcv PointerThingSlice) SelectOther(fn func(*PointerThing) Other) (result []Other) { + for _, v := range rcv { + result = append(result, fn(v)) + } + return +} + +// Shuffle returns a shuffled copy of PointerThingSlice, using a version of the Fisher-Yates shuffle. See: http://clipperhouse.github.io/gen/#Shuffle +func (rcv PointerThingSlice) Shuffle() PointerThingSlice { + numItems := len(rcv) + result := make(PointerThingSlice, numItems) + copy(result, rcv) + for i := 0; i < numItems; i++ { + r := i + rand.Intn(numItems-i) + result[r], result[i] = result[i], result[r] + } + return result +} + +// SumOther sums *PointerThing over elements in PointerThingSlice. See: http://clipperhouse.github.io/gen/#Sum +func (rcv PointerThingSlice) SumOther(fn func(*PointerThing) Other) (result Other) { + for _, v := range rcv { + result += fn(v) + } + return +} + +// Sort implementation based on http://golang.org/pkg/sort/#Sort, see top of this file + +func swapPointerThingSlice(rcv PointerThingSlice, a, b int) { + rcv[a], rcv[b] = rcv[b], rcv[a] +} + +// Insertion sort +func insertionSortPointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, a, b int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && less(rcv[j], rcv[j-1]); j-- { + swapPointerThingSlice(rcv, j, j-1) + } + } +} + +// siftDown implements the heap property on rcv[lo, hi). +// first is an offset into the array where the root of the heap lies. +func siftDownPointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && less(rcv[first+child], rcv[first+child+1]) { + child++ + } + if !less(rcv[first+root], rcv[first+child]) { + return + } + swapPointerThingSlice(rcv, first+root, first+child) + root = child + } +} + +func heapSortPointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownPointerThingSlice(rcv, less, i, hi, first) + } + + // Pop elements, largest first, into end of rcv. + for i := hi - 1; i >= 0; i-- { + swapPointerThingSlice(rcv, first, first+i) + siftDownPointerThingSlice(rcv, less, lo, i, first) + } +} + +// Quicksort, following Bentley and McIlroy, +// Engineering a Sort Function, SP&E November 1993. + +// medianOfThree moves the median of the three values rcv[a], rcv[b], rcv[c] into rcv[a]. +func medianOfThreePointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, a, b, c int) { + m0 := b + m1 := a + m2 := c + // bubble sort on 3 elements + if less(rcv[m1], rcv[m0]) { + swapPointerThingSlice(rcv, m1, m0) + } + if less(rcv[m2], rcv[m1]) { + swapPointerThingSlice(rcv, m2, m1) + } + if less(rcv[m1], rcv[m0]) { + swapPointerThingSlice(rcv, m1, m0) + } + // now rcv[m0] <= rcv[m1] <= rcv[m2] +} + +func swapRangePointerThingSlice(rcv PointerThingSlice, a, b, n int) { + for i := 0; i < n; i++ { + swapPointerThingSlice(rcv, a+i, b+i) + } +} + +func doPivotPointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, lo, hi int) (midlo, midhi int) { + m := lo + (hi-lo)/2 // Written like this to avoid integer overflow. + if hi-lo > 40 { + // Tukey's Ninther, median of three medians of three. + s := (hi - lo) / 8 + medianOfThreePointerThingSlice(rcv, less, lo, lo+s, lo+2*s) + medianOfThreePointerThingSlice(rcv, less, m, m-s, m+s) + medianOfThreePointerThingSlice(rcv, less, hi-1, hi-1-s, hi-1-2*s) + } + medianOfThreePointerThingSlice(rcv, less, lo, m, hi-1) + + // Invariants are: + // rcv[lo] = pivot (set up by ChoosePivot) + // rcv[lo <= i < a] = pivot + // rcv[a <= i < b] < pivot + // rcv[b <= i < c] is unexamined + // rcv[c <= i < d] > pivot + // rcv[d <= i < hi] = pivot + // + // Once b meets c, can swap the "= pivot" sections + // into the middle of the slice. + pivot := lo + a, b, c, d := lo+1, lo+1, hi, hi + for { + for b < c { + if less(rcv[b], rcv[pivot]) { // rcv[b] < pivot + b++ + } else if !less(rcv[pivot], rcv[b]) { // rcv[b] = pivot + swapPointerThingSlice(rcv, a, b) + a++ + b++ + } else { + break + } + } + for b < c { + if less(rcv[pivot], rcv[c-1]) { // rcv[c-1] > pivot + c-- + } else if !less(rcv[c-1], rcv[pivot]) { // rcv[c-1] = pivot + swapPointerThingSlice(rcv, c-1, d-1) + c-- + d-- + } else { + break + } + } + if b >= c { + break + } + // rcv[b] > pivot; rcv[c-1] < pivot + swapPointerThingSlice(rcv, b, c-1) + b++ + c-- + } + + min := func(a, b int) int { + if a < b { + return a + } + return b + } + + n := min(b-a, a-lo) + swapRangePointerThingSlice(rcv, lo, b-n, n) + + n = min(hi-d, d-c) + swapRangePointerThingSlice(rcv, c, hi-n, n) + + return lo + b - a, hi - (d - c) +} + +func quickSortPointerThingSlice(rcv PointerThingSlice, less func(*PointerThing, *PointerThing) bool, a, b, maxDepth int) { + for b-a > 7 { + if maxDepth == 0 { + heapSortPointerThingSlice(rcv, less, a, b) + return + } + maxDepth-- + mlo, mhi := doPivotPointerThingSlice(rcv, less, a, b) + // Avoiding recursion on the larger subproblem guarantees + // a stack depth of at most lg(b-a). + if mlo-a < b-mhi { + quickSortPointerThingSlice(rcv, less, a, mlo, maxDepth) + a = mhi // i.e., quickSortPointerThingSlice(rcv, mhi, b) + } else { + quickSortPointerThingSlice(rcv, less, mhi, b, maxDepth) + b = mlo // i.e., quickSortPointerThingSlice(rcv, a, mlo) + } + } + if b-a > 1 { + insertionSortPointerThingSlice(rcv, less, a, b) + } +} diff --git a/test/select[t]_test.go b/test/select[t]_test.go index cd3692a..2f304cc 100644 --- a/test/select[t]_test.go +++ b/test/select[t]_test.go @@ -23,3 +23,22 @@ func TestSelectOther(t *testing.T) { t.Errorf("SelectOther should result in %v, got %v", expected1, select1) } } + +func TestPointerSelectOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + select1 := things.SelectOther(number) + expected1 := []Other{60, -20, 100} + + if !reflect.DeepEqual(select1, expected1) { + t.Errorf("SelectOther should result in %v, got %v", expected1, select1) + } +} diff --git a/test/shuffle_test.go b/test/shuffle_test.go index 01185b6..3a82d9c 100644 --- a/test/shuffle_test.go +++ b/test/shuffle_test.go @@ -24,3 +24,26 @@ func TestShuffle(t *testing.T) { t.Error("The shuffled slice is missing elements") } } + +func TestPointerShuffle(t *testing.T) { + original := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + shuffled := original.Shuffle() + + notFound := func(t *PointerThing) bool { + for x := 0; x < len(shuffled); x++ { + if shuffled[x] == t { + return false + } + } + return true + } + + if original.Any(notFound) { + t.Error("The shuffled slice is missing elements") + } +} diff --git a/test/single_test.go b/test/single_test.go index f45c358..75bbe45 100644 --- a/test/single_test.go +++ b/test/single_test.go @@ -47,3 +47,49 @@ func TestSingle(t *testing.T) { t.Errorf("Single should fail on empty slice") } } + +func TestPointerSingle(t *testing.T) { + things := PointerThingSlice{ + {"First", 0}, + {"Second", 0}, + {"Third", 0}, + {"Second", 1}, + } + + s1, err1 := things.Single(func(x *PointerThing) bool { + return x.Name == "Third" + }) + + if err1 != nil { + t.Errorf("Single should succeed when finding Name == Third") + } + + expected1 := &PointerThing{"Third", 0} + if *s1 != *expected1 { + t.Errorf("Single should find %v, but found %v", expected1, s1) + } + + _, err2 := things.Single(func(x *PointerThing) bool { + return x.Name == "Second" + }) + + if err2 == nil { + t.Errorf("Single should fail when finding Name == Second") + } + + _, err3 := things.Single(func(x *PointerThing) bool { + return x.Name == "Dummy" + }) + + if err3 == nil { + t.Errorf("Single should fail when finding Name == Dummy") + } + + _, err4 := PointerThingSlice{}.First(func(x *PointerThing) bool { + return true + }) + + if err4 == nil { + t.Errorf("Single should fail on empty slice") + } +} diff --git a/test/sortby_test.go b/test/sortby_test.go index bc810e2..459b6e9 100644 --- a/test/sortby_test.go +++ b/test/sortby_test.go @@ -111,3 +111,110 @@ func appendMany(x Thing, n int) (result ThingSlice) { } return } + +func TestPointerSortBy(t *testing.T) { + first := &PointerThing{"First", 60} + second := &PointerThing{"Second", 40} + third := &PointerThing{"Third", 100} + anotherThird := &PointerThing{"Third", 100} + fourth := &PointerThing{"Fourth", 40} + fifth := &PointerThing{"Fifth", 70} + sixth := &PointerThing{"Sixth", 10} + seventh := &PointerThing{"Seventh", 50} + eighth := &PointerThing{"Eighth", 110} + + things := PointerThingSlice{ + first, + second, + third, + anotherThird, + fourth, + } + + lotsOfThings := PointerThingSlice{ + first, + second, + third, + fourth, + fifth, + sixth, + seventh, + eighth, + } + + name := func(a, b *PointerThing) bool { + return a.Name < b.Name + } + + sort1 := things.SortBy(name) + + sorted1 := PointerThingSlice{first, fourth, second, third, anotherThird} + + if !reflect.DeepEqual(sort1, PointerThingSlice{first, fourth, second, third, anotherThird}) { + t.Errorf("SortBy name should be %v, got %v", sorted1, sort1) + } + + if !sort1.IsSortedBy(name) { + t.Errorf("IsSortedBy name should be true") + } + + if things.IsSortedBy(name) { + t.Errorf("things should not be sorted by name") + } + + sort2 := things.SortByDesc(name) + + sorted2 := PointerThingSlice{anotherThird, third, second, fourth, first} + + if !reflect.DeepEqual(sort2, sorted2) { + t.Errorf("SortByDesc name should be %v, got %v", sorted2, sort2) + } + + if !sort2.IsSortedByDesc(name) { + t.Errorf("IsSortedByDesc name should be true %v", sort2) + } + + if things.IsSortedByDesc(name) { + t.Errorf("things should not be sorted desc by name") + } + + // intended to hit threshold to invoke quicksort (7) + sort3 := lotsOfThings.SortBy(name) + + sorted3 := PointerThingSlice{eighth, fifth, first, fourth, second, seventh, sixth, third} + + if !reflect.DeepEqual(sort3, sorted3) { + t.Errorf("Sort name should be %v, got %v", sorted3, sort3) + } + + // intended to hit threshold to invoke medianOfThree (40) + var evenMore PointerThingSlice + evenMore = append(evenMore, lotsOfThings...) + evenMore = append(evenMore, lotsOfThings...) + evenMore = append(evenMore, lotsOfThings...) + evenMore = append(evenMore, lotsOfThings...) + evenMore = append(evenMore, lotsOfThings...) + evenMore = append(evenMore, lotsOfThings...) + + sort4 := evenMore.SortBy(name) + + sorted4 := PointerThingSlice{eighth, eighth, eighth, eighth, eighth, eighth} + sorted4 = append(sorted4, appendManyP(fifth, 6)...) + sorted4 = append(sorted4, appendManyP(first, 6)...) + sorted4 = append(sorted4, appendManyP(fourth, 6)...) + sorted4 = append(sorted4, appendManyP(second, 6)...) + sorted4 = append(sorted4, appendManyP(seventh, 6)...) + sorted4 = append(sorted4, appendManyP(sixth, 6)...) + sorted4 = append(sorted4, appendManyP(third, 6)...) + + if !reflect.DeepEqual(sort4, sorted4) { + t.Errorf("Sort name should be %v, got %v", sorted3, sort3) + } +} + +func appendManyP(x *PointerThing, n int) (result PointerThingSlice) { + for i := 0; i < n; i++ { + result = append(result, x) + } + return +} diff --git a/test/sum[t]_test.go b/test/sum[t]_test.go index d493ad6..ee92cdb 100644 --- a/test/sum[t]_test.go +++ b/test/sum[t]_test.go @@ -19,3 +19,21 @@ func TestSumOther(t *testing.T) { t.Errorf("SumOther should result in %v, got %v", 340, sum1) } } + +func TestPointerSumOther(t *testing.T) { + things := PointerThingSlice{ + {"First", 60}, + {"Second", -20}, + {"Third", 100}, + } + + number := func(x *PointerThing) Other { + return x.Number + } + + sum1 := things.SumOther(number) + + if sum1 != 140 { + t.Errorf("SumOther should result in %v, got %v", 340, sum1) + } +} diff --git a/test/thing.go b/test/thing.go index b0bba9e..91ccd3a 100644 --- a/test/thing.go +++ b/test/thing.go @@ -5,3 +5,9 @@ type Thing struct { Name string Number Other } + +// +test * slice:"Any,All,Count,Distinct,DistinctBy,Each,First,MaxBy,MinBy,Single,Where,SortBy,SortByDesc,IsSortedBy,IsSortedByDesc,Aggregate[Other],Average[Other],GroupBy[Other],Max[Other],Min[Other],Select[Other],Shuffle,Sum[Other]" +type PointerThing struct { + Name string + Number Other +} diff --git a/test/where_test.go b/test/where_test.go index afb4ced..07066bf 100644 --- a/test/where_test.go +++ b/test/where_test.go @@ -42,3 +42,41 @@ func TestWhere(t *testing.T) { t.Errorf("Where should result in empty slice, got %v", where3) } } + +func TestPointerWhere(t *testing.T) { + things := PointerThingSlice{ + {"First", 0}, + {"Second", 0}, + {"Third", 0}, + {"Second", 10}, + } + + where1 := things.Where(func(x *PointerThing) bool { + return x.Name == "Second" + }) + + expected1 := PointerThingSlice{ + {"Second", 0}, + {"Second", 10}, + } + + if !reflect.DeepEqual(where1, expected1) { + t.Errorf("Where should result in %v, got %v", expected1, where1) + } + + where2 := things.Where(func(x *PointerThing) bool { + return x.Name == "Dummy" + }) + + if len(where2) != 0 { + t.Errorf("Where should result in empty slice, got %v", where2) + } + + where3 := PointerThingSlice{}.Where(func(x *PointerThing) bool { + return true + }) + + if len(where3) != 0 { + t.Errorf("Where should result in empty slice, got %v", where3) + } +}