diff --git a/v2/of_numeric.go b/v2/of_numeric.go index 1507b24..ff65c50 100644 --- a/v2/of_numeric.go +++ b/v2/of_numeric.go @@ -229,6 +229,10 @@ func (o OfNumericSlice[T]) Unique() OfNumericSlice[T] { return OfNumericSlice[T]{Unique(o.Result)} } +func (o OfNumericSlice[T]) UniqueStable() OfNumericSlice[T] { + return OfNumericSlice[T]{UniqueStable(o.Result)} +} + func (o OfNumericSlice[T]) Unshift(elements ...T) OfNumericSlice[T] { return OfNumericSlice[T]{Unshift(o.Result, elements...)} } diff --git a/v2/of_numeric_test.go b/v2/of_numeric_test.go index daf7648..e41a025 100644 --- a/v2/of_numeric_test.go +++ b/v2/of_numeric_test.go @@ -1,9 +1,10 @@ package pie_test import ( + "testing" + "github.com/elliotchance/pie/v2" "github.com/stretchr/testify/assert" - "testing" ) func TestOfONumeric(t *testing.T) { @@ -31,4 +32,12 @@ func TestOfONumeric(t *testing.T) { assert.Equal(t, []float64{1.23}, names) }) + + t.Run("unique_stable", func(t *testing.T) { + names := pie.OfNumeric([]float64{-4.56, 1.23, -4.56}). + UniqueStable(). + Result + + assert.Equal(t, []float64{-4.56, 1.23}, names) + }) } diff --git a/v2/of_ordered.go b/v2/of_ordered.go index c1160dd..43032ea 100644 --- a/v2/of_ordered.go +++ b/v2/of_ordered.go @@ -197,6 +197,10 @@ func (o OfOrderedSlice[T]) Unique() OfOrderedSlice[T] { return OfOrderedSlice[T]{Unique(o.Result)} } +func (o OfOrderedSlice[T]) UniqueStable() OfOrderedSlice[T] { + return OfOrderedSlice[T]{UniqueStable(o.Result)} +} + func (o OfOrderedSlice[T]) Unshift(elements ...T) OfOrderedSlice[T] { return OfOrderedSlice[T]{Unshift(o.Result, elements...)} } diff --git a/v2/of_ordered_test.go b/v2/of_ordered_test.go index 44fc676..561825e 100644 --- a/v2/of_ordered_test.go +++ b/v2/of_ordered_test.go @@ -1,9 +1,10 @@ package pie_test import ( + "testing" + "github.com/elliotchance/pie/v2" "github.com/stretchr/testify/assert" - "testing" ) func TestOfOrdered(t *testing.T) { @@ -31,4 +32,12 @@ func TestOfOrdered(t *testing.T) { assert.Equal(t, []string{"Bob", "Sally"}, names) }) + + t.Run("unique_stable", func(t *testing.T) { + names := pie.OfNumeric([]float64{-4.56, 1.23, -4.56}). + UniqueStable(). + Result + + assert.Equal(t, []float64{-4.56, 1.23}, names) + }) } diff --git a/v2/unique_stable.go b/v2/unique_stable.go new file mode 100644 index 0000000..dec6e5d --- /dev/null +++ b/v2/unique_stable.go @@ -0,0 +1,24 @@ +package pie + +// UniqueStable works similar to Unique. However, unlike Unique +// the slice returned will be in previous relative order +func UniqueStable[T comparable](ss []T) []T { + // Avoid the allocation. If there is one element or less it is already + // unique. + if len(ss) < 2 { + return ss + } + + seen := map[T]struct{}{} + ret := make([]T, 0) + + for _, value := range ss { + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + ret = append(ret, value) + } + + return ret +} diff --git a/v2/unique_stable_test.go b/v2/unique_stable_test.go new file mode 100644 index 0000000..b194759 --- /dev/null +++ b/v2/unique_stable_test.go @@ -0,0 +1,42 @@ +package pie_test + +import ( + "testing" + + "github.com/elliotchance/pie/v2" + "github.com/stretchr/testify/assert" +) + +var uniqueStableTests = []struct { + ss []float64 + uniqueStable []float64 +}{ + { + nil, + nil, + }, + { + []float64{}, + []float64{}, + }, + { + []float64{789}, + []float64{789}, + }, + { + []float64{12.789, -13.2, 12.789}, + []float64{12.789, -13.2}, + }, + { + []float64{12.789, -13.2, 1.234e6, 789}, + []float64{12.789, -13.2, 1.234e6, 789}, + }, +} + +func TestUniqueStable(t *testing.T) { + for _, test := range uniqueStableTests { + t.Run("", func(t *testing.T) { + assert.Equal(t, test.uniqueStable, pie.UniqueStable(test.ss)) + }) + } +}