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