slices: add Replace
This CL adds a strightforward implementation of a Replace function.
Benchmark:
name time/op
Replace/naive-fast-8 144ns ± 2%
Replace/optimized-fast-8 136ns ± 1%
Replace/naive-slow-8 267ns ± 8%
Replace/optimized-slow-8 252ns ±14%
Fixes golang/go#55358
Change-Id: Ibf7187a890385a454c5b5daff9f731da11e6fd72
Reviewed-on: https://go-review.googlesource.com/c/exp/+/444682
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Run-TryBot: Changkun Ou <mail@changkun.de>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/slices/slices.go b/slices/slices.go
index a9fe63f..0c756c4 100644
--- a/slices/slices.go
+++ b/slices/slices.go
@@ -162,6 +162,23 @@
return append(s[:i], s[j:]...)
}
+// Replace replaces the elements s[i:j] by the given v, and returns the
+// modified slice. Replace panics if s[i:j] is not a valid slice of s.
+func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
+ tot := len(s[:i]) + len(v) + len(s[j:])
+ if tot <= cap(s) {
+ s2 := s[:tot]
+ copy(s2[i+len(v):], s[j:])
+ copy(s2[i:], v)
+ return s2
+ }
+ s2 := make(S, tot)
+ copy(s2, s[:i])
+ copy(s2[i:], v)
+ copy(s2[i+len(v):], s[j:])
+ return s2
+}
+
// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
diff --git a/slices/slices_test.go b/slices/slices_test.go
index 97c4718..367dcf1 100644
--- a/slices/slices_test.go
+++ b/slices/slices_test.go
@@ -614,3 +614,99 @@
t.Errorf("cap(Clip(%v)) = %d, want 3", orig, cap(s2))
}
}
+
+// naiveReplace is a baseline implementation to the Replace function.
+func naiveReplace[S ~[]E, E any](s S, i, j int, v ...E) S {
+ s = Delete(s, i, j)
+ s = Insert(s, i, v...)
+ return s
+}
+
+func TestReplace(t *testing.T) {
+ for _, test := range []struct {
+ s, v []int
+ i, j int
+ }{
+ {}, // all zero value
+ {
+ s: []int{1, 2, 3, 4},
+ v: []int{5},
+ i: 1,
+ j: 2,
+ },
+ {
+ s: []int{1, 2, 3, 4},
+ v: []int{5, 6, 7, 8},
+ i: 1,
+ j: 2,
+ },
+ {
+ s: func() []int {
+ s := make([]int, 3, 20)
+ s[0] = 0
+ s[1] = 1
+ s[2] = 2
+ return s
+ }(),
+ v: []int{3, 4, 5, 6, 7},
+ i: 0,
+ j: 1,
+ },
+ } {
+ ss, vv := Clone(test.s), Clone(test.v)
+ want := naiveReplace(ss, test.i, test.j, vv...)
+ got := Replace(test.s, test.i, test.j, test.v...)
+ if !Equal(got, want) {
+ t.Errorf("Replace(%v, %v, %v, %v) = %v, want %v", test.s, test.i, test.j, test.v, got, want)
+ }
+ }
+}
+
+func BenchmarkReplace(b *testing.B) {
+ cases := []struct {
+ name string
+ s, v func() []int
+ i, j int
+ }{
+ {
+ name: "fast",
+ s: func() []int {
+ return make([]int, 100)
+ },
+ v: func() []int {
+ return make([]int, 20)
+ },
+ i: 10,
+ j: 40,
+ },
+ {
+ name: "slow",
+ s: func() []int {
+ return make([]int, 100)
+ },
+ v: func() []int {
+ return make([]int, 20)
+ },
+ i: 0,
+ j: 2,
+ },
+ }
+
+ for _, c := range cases {
+ b.Run("naive-"+c.name, func(b *testing.B) {
+ for k := 0; k < b.N; k++ {
+ s := c.s()
+ v := c.v()
+ _ = naiveReplace(s, c.i, c.j, v...)
+ }
+ })
+ b.Run("optimized-"+c.name, func(b *testing.B) {
+ for k := 0; k < b.N; k++ {
+ s := c.s()
+ v := c.v()
+ _ = Replace(s, c.i, c.j, v...)
+ }
+ })
+ }
+
+}