Repository: elliotchance/pie
Pull request #198 (2023-09-07 01:11:38)

added rotate
I had a need to treat a slice as a circular shift register. Rotate permits circular shifts (i.e., rotates) to left or right.

SlothNinja: Still new to generating/updating pull requests. I added the requested feature to the rotate branch. Not sure what I need to do to update the pull request.

Patch:
diff --git a/v2/of.go b/v2/of.go
index e6f44e9..1302037 100644
--- a/v2/of.go
+++ b/v2/of.go
@@ -136,6 +136,13 @@ func (o OfSlice[T]) Reverse() OfSlice[T] {
 	return OfSlice[T]{Reverse(o.Result)}
 }
 
+// Rotate returns slice circularly rotated by a number of positions n.
+// If n is positive, the slice is rotated right.
+// If n is negative, the slice is rotated left.
+func (o OfSlice[T]) Rotate(n int) OfSlice[T] {
+	return OfSlice[T]{Rotate(o.Result, n)}
+}
+
 // Send sends elements to channel
 // in normal act it sends all elements but if func canceled it can be less
 //
diff --git a/v2/rotate.go b/v2/rotate.go
new file mode 100644
index 0000000..7c85d23
--- /dev/null
+++ b/v2/rotate.go
@@ -0,0 +1,30 @@
+package pie
+
+// Rotate return slice circularly rotated by a number of positions n.
+// If n is positive, the slice is rotated right.
+// If n is negative, the slice is rotated left.
+func Rotate[T any](ss []T, n int) []T {
+
+	length := len(ss)
+
+	// Avoid the allocation.
+	// If there is one element or less, then already rotated.
+	if length < 2 {
+		return ss
+	}
+
+	// Normalize shift
+	// no div by 0 since length >= 2
+	shift := -n % length
+	if shift < 0 {
+		shift = length + shift
+	}
+
+	// Avoid the allocation.
+	// If normalized shift is 0, then already rotated.
+	if shift == 0 {
+		return ss
+	}
+
+	return append(DropTop(ss, shift), Top(ss, shift)...)
+}


Test patch:
diff --git a/v2/of_test.go b/v2/of_test.go
index 32d8e67..9067464 100644
--- a/v2/of_test.go
+++ b/v2/of_test.go
@@ -1,10 +1,11 @@
 package pie_test
 
 import (
-	"github.com/elliotchance/pie/v2"
-	"github.com/stretchr/testify/assert"
 	"strings"
 	"testing"
+
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestOf(t *testing.T) {
@@ -36,4 +37,12 @@ func TestOf(t *testing.T) {
 
 		assert.Equal(t, []string{"Bob", "Sally"}, names)
 	})
+
+	t.Run("rotate", func(t *testing.T) {
+		names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}).
+			Rotate(1).
+			Result
+
+		assert.Equal(t, []string{"Jane", "Bob", "Sally", "John"}, names)
+	})
 }
diff --git a/v2/rotate_test.go b/v2/rotate_test.go
new file mode 100644
index 0000000..a5e18dc
--- /dev/null
+++ b/v2/rotate_test.go
@@ -0,0 +1,89 @@
+package pie_test
+
+import (
+	"testing"
+
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+)
+
+var rotateTests = []struct {
+	ss      []float64
+	rotated []float64
+	n       int
+}{
+	{
+		nil,
+		nil,
+		0,
+	},
+	{
+		[]float64{1.23, 2.34},
+		[]float64{1.23, 2.34},
+		0,
+	},
+	{
+		[]float64{},
+		[]float64{},
+		0,
+	},
+	{
+		[]float64{},
+		[]float64{},
+		3,
+	},
+	{
+		[]float64{1.23, 2.34},
+		[]float64{2.34, 1.23},
+		1,
+	},
+	{
+		[]float64{1.23, 2.34},
+		[]float64{2.34, 1.23},
+		-1,
+	},
+	{
+		[]float64{1.23},
+		[]float64{1.23},
+		1000,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{1.23, 2.34, 3.45},
+		3,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{1.23, 2.34, 3.45},
+		-3,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{1.23, 2.34, 3.45},
+		6,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{1.23, 2.34, 3.45},
+		-6,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{2.34, 3.45, 1.23},
+		-1,
+	},
+	{
+		[]float64{1.23, 2.34, 3.45},
+		[]float64{3.45, 1.23, 2.34},
+		1,
+	},
+}
+
+func TestRotate(t *testing.T) {
+	for _, test := range rotateTests {
+		t.Run("", func(t *testing.T) {
+			rotated := pie.Rotate(test.ss, test.n)
+			assert.Equal(t, test.rotated, rotated)
+		})
+	}
+}