From aa5d6ffd9d02bfaeb4a90b0428df93ff85d28b0f Mon Sep 17 00:00:00 2001 From: Valentin Deleplace Date: Wed, 8 May 2019 01:21:23 +0200 Subject: [PATCH] added: SortStableUsing and SortUsing (#103) SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the slice returned will be reallocated as to not modify the input slice. SortUsing works similar to sort.Slice. However, unlike sort.Slice the slice returned will be reallocated as to not modify the input slice. Fixes #98 --- README.md | 2 + functions/main.go | 2 + functions/sort.go | 2 +- functions/sort_stable_using.go | 23 +++++++++++ functions/sort_using.go | 23 +++++++++++ pie/carpointers_pie.go | 37 +++++++++++++++++ pie/carpointers_test.go | 72 ++++++++++++++++++++++++++++++++++ pie/cars_pie.go | 37 +++++++++++++++++ pie/cars_test.go | 72 ++++++++++++++++++++++++++++++++++ pie/float64s_pie.go | 2 +- pie/ints_pie.go | 2 +- pie/strings_pie.go | 38 +++++++++++++++++- pie/strings_test.go | 59 ++++++++++++++++++++++++++++ template.go | 50 ++++++++++++++++++++++- 14 files changed, 416 insertions(+), 5 deletions(-) create mode 100644 functions/sort_stable_using.go create mode 100644 functions/sort_using.go diff --git a/README.md b/README.md index f223efa..c4d4b5c 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,8 @@ This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`. | `Reverse` | ✓ | ✓ | ✓ | | n | Reverse elements. | | `Send` | ✓ | ✓ | ✓ | | n | Send all element to channel. | | `Sort` | ✓ | ✓ | | | n⋅log(n) | Return a new sorted slice. | +| `SortUsing` | ✓ | | ✓ | | n⋅log(n) | Return a new sorted slice, using custom comparator. | +| `SortStableUsing` | ✓ | | ✓ | | n⋅log(n) | Return a new sorted slice, using custom comparator, keeping the original order of equal elements. | | `Sum` | | ✓ | | | n | Sum (total) of all elements. | | `Shuffle` | ✓ | ✓ | ✓ | | n | Returns a new shuffled slice. | | `Top` | ✓ | ✓ | ✓ | | n | Gets several elements from top(head of slice).| diff --git a/functions/main.go b/functions/main.go index 50feb2e..c0aeb32 100644 --- a/functions/main.go +++ b/functions/main.go @@ -50,6 +50,8 @@ var Functions = []struct { {"Reverse", "reverse.go", ForAll}, {"Send", "send.go", ForAll}, {"Sort", "sort.go", ForNumbersAndStrings}, + {"SortUsing", "sort_using.go", ForStrings | ForStructs}, + {"SortStableUsing", "sort_stable_using.go", ForStrings | ForStructs}, {"Sum", "sum.go", ForNumbers}, {"Shuffle", "shuffle.go", ForAll}, {"Top", "top.go", ForAll}, diff --git a/functions/sort.go b/functions/sort.go index 9423236..4bc1d9b 100644 --- a/functions/sort.go +++ b/functions/sort.go @@ -15,7 +15,7 @@ func (ss SliceType) Sort() SliceType { return ss } - sorted := make([]ElementType, len(ss)) + sorted := make(SliceType, len(ss)) copy(sorted, ss) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] diff --git a/functions/sort_stable_using.go b/functions/sort_stable_using.go new file mode 100644 index 0000000..81a26b2 --- /dev/null +++ b/functions/sort_stable_using.go @@ -0,0 +1,23 @@ +package functions + +import ( + "sort" +) + +// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the +// slice returned will be reallocated as to not modify the input slice. +func (ss SliceType) SortStableUsing(less func(a, b ElementType) bool) SliceType { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(SliceType, len(ss)) + copy(sorted, ss) + sort.SliceStable(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} diff --git a/functions/sort_using.go b/functions/sort_using.go new file mode 100644 index 0000000..a8ed393 --- /dev/null +++ b/functions/sort_using.go @@ -0,0 +1,23 @@ +package functions + +import ( + "sort" +) + +// SortUsing works similar to sort.Slice. However, unlike sort.Slice the +// slice returned will be reallocated as to not modify the input slice. +func (ss SliceType) SortUsing(less func(a, b ElementType) bool) SliceType { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(SliceType, len(ss)) + copy(sorted, ss) + sort.Slice(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} diff --git a/pie/carpointers_pie.go b/pie/carpointers_pie.go index 617163d..c43b3af 100755 --- a/pie/carpointers_pie.go +++ b/pie/carpointers_pie.go @@ -5,6 +5,7 @@ import ( "encoding/json" "github.com/elliotchance/pie/pie/util" "math/rand" + "sort" ) // All will return true if all callbacks return true. It follows the same logic @@ -259,6 +260,42 @@ func (ss carPointers) Send(ctx context.Context, ch chan<- *car) carPointers { return ss } +// SortUsing works similar to sort.Slice. However, unlike sort.Slice the +// slice returned will be reallocated as to not modify the input slice. +func (ss carPointers) SortUsing(less func(a, b *car) bool) carPointers { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(carPointers, len(ss)) + copy(sorted, ss) + sort.Slice(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + +// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the +// slice returned will be reallocated as to not modify the input slice. +func (ss carPointers) SortStableUsing(less func(a, b *car) bool) carPointers { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(carPointers, len(ss)) + copy(sorted, ss) + sort.SliceStable(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + // Shuffle returns shuffled slice by your rand.Source func (ss carPointers) Shuffle(source rand.Source) carPointers { n := len(ss) diff --git a/pie/carpointers_test.go b/pie/carpointers_test.go index c5ce348..7adbf17 100644 --- a/pie/carpointers_test.go +++ b/pie/carpointers_test.go @@ -326,6 +326,78 @@ var stringsToStringsTests = []struct { }, } +var carPointersSortCustomTests = []struct { + ss carPointers + sortedStableByName carPointers + sortedStableByColor carPointers +}{ + { + nil, + nil, + nil, + }, + { + carPointers{}, + carPointers{}, + carPointers{}, + }, + { + carPointers{&car{"foo", "red"}}, + carPointers{&car{"foo", "red"}}, + carPointers{&car{"foo", "red"}}, + }, + { + carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"foo", "red"}}, + carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}}, + carPointers{&car{"Baz", "black"}, &car{"foo", "red"}, &car{"bar", "yellow"}}, + }, + { + carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}}, + carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}, &car{"qux", "cyan"}}, + carPointers{&car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}, &car{"bar", "yellow"}}, + }, + { + carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}}, + carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}}, + carPointers{&car{"aaa", "black"}, &car{"bbb", "black"}, &car{"aaa", "yellow"}, &car{"bbb", "yellow"}}, + }, +} + +func carPointerNameLess(a, b *car) bool { + return a.Name < b.Name +} + +func carPointerColorLess(a, b *car) bool { + return a.Color < b.Color +} + +func TestCarPointers_SortUsing(t *testing.T) { + isSortedUsing := func(ss carPointers, less func(a, b *car) bool) bool { + for i := 1; i < len(ss); i++ { + if less(ss[i], ss[i-1]) { + return false + } + } + return true + } + + for _, test := range carPointersSortCustomTests { + t.Run("", func(t *testing.T) { + defer assertImmutableCarPointers(t, &test.ss)() + + sortedByName := test.ss.SortUsing(carPointerNameLess) + assert.True(t, isSortedUsing(sortedByName, carPointerNameLess)) + sortedStableByName := test.ss.SortStableUsing(carPointerNameLess) + assert.Equal(t, test.sortedStableByName, sortedStableByName) + + sortedByColor := test.ss.SortUsing(carPointerColorLess) + assert.True(t, isSortedUsing(sortedByColor, carPointerColorLess)) + sortedStableByColor := test.ss.SortStableUsing(carPointerColorLess) + assert.Equal(t, test.sortedStableByColor, sortedStableByColor) + }) + } +} + func TestCarPointers_ToStrings(t *testing.T) { for _, test := range stringsToStringsTests { t.Run("", func(t *testing.T) { diff --git a/pie/cars_pie.go b/pie/cars_pie.go index e783b1a..c4c8ff8 100755 --- a/pie/cars_pie.go +++ b/pie/cars_pie.go @@ -5,6 +5,7 @@ import ( "encoding/json" "github.com/elliotchance/pie/pie/util" "math/rand" + "sort" ) // All will return true if all callbacks return true. It follows the same logic @@ -259,6 +260,42 @@ func (ss cars) Send(ctx context.Context, ch chan<- car) cars { return ss } +// SortUsing works similar to sort.Slice. However, unlike sort.Slice the +// slice returned will be reallocated as to not modify the input slice. +func (ss cars) SortUsing(less func(a, b car) bool) cars { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(cars, len(ss)) + copy(sorted, ss) + sort.Slice(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + +// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the +// slice returned will be reallocated as to not modify the input slice. +func (ss cars) SortStableUsing(less func(a, b car) bool) cars { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(cars, len(ss)) + copy(sorted, ss) + sort.SliceStable(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + // Shuffle returns shuffled slice by your rand.Source func (ss cars) Shuffle(source rand.Source) cars { n := len(ss) diff --git a/pie/cars_test.go b/pie/cars_test.go index 7706f94..674d81e 100644 --- a/pie/cars_test.go +++ b/pie/cars_test.go @@ -288,6 +288,78 @@ func TestCars_Reverse(t *testing.T) { } } +var carsSortCustomTests = []struct { + ss cars + sortedStableByName cars + sortedStableByColor cars +}{ + { + nil, + nil, + nil, + }, + { + cars{}, + cars{}, + cars{}, + }, + { + cars{car{"foo", "red"}}, + cars{car{"foo", "red"}}, + cars{car{"foo", "red"}}, + }, + { + cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"foo", "red"}}, + cars{car{"Baz", "black"}, car{"bar", "yellow"}, car{"foo", "red"}}, + cars{car{"Baz", "black"}, car{"foo", "red"}, car{"bar", "yellow"}}, + }, + { + cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}}, + cars{car{"Baz", "black"}, car{"bar", "yellow"}, car{"foo", "red"}, car{"qux", "cyan"}}, + cars{car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}, car{"bar", "yellow"}}, + }, + { + cars{car{"aaa", "yellow"}, car{"aaa", "black"}, car{"bbb", "yellow"}, car{"bbb", "black"}}, + cars{car{"aaa", "yellow"}, car{"aaa", "black"}, car{"bbb", "yellow"}, car{"bbb", "black"}}, + cars{car{"aaa", "black"}, car{"bbb", "black"}, car{"aaa", "yellow"}, car{"bbb", "yellow"}}, + }, +} + +func carNameLess(a, b car) bool { + return a.Name < b.Name +} + +func carColorLess(a, b car) bool { + return a.Color < b.Color +} + +func TestCars_SortUsing(t *testing.T) { + isSortedUsing := func(ss cars, less func(a, b car) bool) bool { + for i := 1; i < len(ss); i++ { + if less(ss[i], ss[i-1]) { + return false + } + } + return true + } + + for _, test := range carsSortCustomTests { + t.Run("", func(t *testing.T) { + defer assertImmutableCars(t, &test.ss)() + + sortedByName := test.ss.SortUsing(carNameLess) + assert.True(t, isSortedUsing(sortedByName, carNameLess)) + sortedStableByName := test.ss.SortStableUsing(carNameLess) + assert.Equal(t, test.sortedStableByName, sortedStableByName) + + sortedByColor := test.ss.SortUsing(carColorLess) + assert.True(t, isSortedUsing(sortedByColor, carColorLess)) + sortedStableByColor := test.ss.SortStableUsing(carColorLess) + assert.Equal(t, test.sortedStableByColor, sortedStableByColor) + }) + } +} + var carsToStringsTests = []struct { ss cars transform func(car) string diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go index e6cfa60..8bba2c2 100755 --- a/pie/float64s_pie.go +++ b/pie/float64s_pie.go @@ -425,7 +425,7 @@ func (ss Float64s) Sort() Float64s { return ss } - sorted := make([]float64, len(ss)) + sorted := make(Float64s, len(ss)) copy(sorted, ss) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] diff --git a/pie/ints_pie.go b/pie/ints_pie.go index 4542326..9fba99d 100755 --- a/pie/ints_pie.go +++ b/pie/ints_pie.go @@ -425,7 +425,7 @@ func (ss Ints) Sort() Ints { return ss } - sorted := make([]int, len(ss)) + sorted := make(Ints, len(ss)) copy(sorted, ss) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] diff --git a/pie/strings_pie.go b/pie/strings_pie.go index 928648b..36fe179 100755 --- a/pie/strings_pie.go +++ b/pie/strings_pie.go @@ -381,7 +381,7 @@ func (ss Strings) Sort() Strings { return ss } - sorted := make([]string, len(ss)) + sorted := make(Strings, len(ss)) copy(sorted, ss) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] @@ -390,6 +390,42 @@ func (ss Strings) Sort() Strings { return sorted } +// SortUsing works similar to sort.Slice. However, unlike sort.Slice the +// slice returned will be reallocated as to not modify the input slice. +func (ss Strings) SortUsing(less func(a, b string) bool) Strings { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(Strings, len(ss)) + copy(sorted, ss) + sort.Slice(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + +// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the +// slice returned will be reallocated as to not modify the input slice. +func (ss Strings) SortStableUsing(less func(a, b string) bool) Strings { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(Strings, len(ss)) + copy(sorted, ss) + sort.SliceStable(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} + // Shuffle returns shuffled slice by your rand.Source func (ss Strings) Shuffle(source rand.Source) Strings { n := len(ss) diff --git a/pie/strings_test.go b/pie/strings_test.go index 909ba21..375559e 100644 --- a/pie/strings_test.go +++ b/pie/strings_test.go @@ -320,6 +320,65 @@ func TestStrings_AreSorted(t *testing.T) { } } +func stringShorter(a, b string) bool { + return len(a) < len(b) +} + +var stringsSortByLengthTests = []struct { + ss Strings + sortedStable Strings +}{ + { + nil, + nil, + }, + { + Strings{}, + Strings{}, + }, + { + Strings{"foo"}, + Strings{"foo"}, + }, + { + Strings{"aaa", "b", "cc"}, + Strings{"b", "cc", "aaa"}, + }, + { + Strings{"zz", "aaa", "b", "cc"}, + Strings{"b", "zz", "cc", "aaa"}, + }, +} + +func TestStrings_SortUsing(t *testing.T) { + isSortedByLength := func(ss Strings) bool { + for i := 1; i < len(ss); i++ { + if stringShorter(ss[i], ss[i-1]) { + return false + } + } + return true + } + less := stringShorter + for _, test := range stringsSortByLengthTests { + t.Run("", func(t *testing.T) { + defer assertImmutableStrings(t, &test.ss)() + sortedCustom := test.ss.SortUsing(less) + assert.True(t, isSortedByLength(sortedCustom)) + }) + } +} + +func TestStrings_SortStableUsing(t *testing.T) { + less := stringShorter + for _, test := range stringsSortByLengthTests { + t.Run("", func(t *testing.T) { + defer assertImmutableStrings(t, &test.ss)() + assert.Equal(t, test.sortedStable, test.ss.SortStableUsing(less)) + }) + } +} + var stringsUniqueTests = []struct { ss Strings unique Strings diff --git a/template.go b/template.go index 2c5a0a1..e2d69d1 100644 --- a/template.go +++ b/template.go @@ -570,7 +570,7 @@ func (ss SliceType) Sort() SliceType { return ss } - sorted := make([]ElementType, len(ss)) + sorted := make(SliceType, len(ss)) copy(sorted, ss) sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] @@ -578,6 +578,54 @@ func (ss SliceType) Sort() SliceType { return sorted } +`, + "SortStableUsing": `package functions + +import ( + "sort" +) + +// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the +// slice returned will be reallocated as to not modify the input slice. +func (ss SliceType) SortStableUsing(less func(a, b ElementType) bool) SliceType { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(SliceType, len(ss)) + copy(sorted, ss) + sort.SliceStable(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} +`, + "SortUsing": `package functions + +import ( + "sort" +) + +// SortUsing works similar to sort.Slice. However, unlike sort.Slice the +// slice returned will be reallocated as to not modify the input slice. +func (ss SliceType) SortUsing(less func(a, b ElementType) bool) SliceType { + // Avoid the allocation. If there is one element or less it is already + // sorted. + if len(ss) < 2 { + return ss + } + + sorted := make(SliceType, len(ss)) + copy(sorted, ss) + sort.Slice(sorted, func(i, j int) bool { + return less(sorted[i], sorted[j]) + }) + + return sorted +} `, "Sum": `package functions