Repository: elliotchance/pie
Pull request #203 (2024-07-30 16:04:02)

feat: Add a new slice method: UniqueStable
In my business development, I need to deduplicate a slice and return the remaining elements in the previous order. I didn't find a similar implementation in pie, so I added it and named it UniqueStable.


Patch:
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_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/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
+}


Test patch:
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_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_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))
+		})
+	}
+}