Skip to content

Commit

Permalink
slices: add Concat
Browse files Browse the repository at this point in the history
Fixes golang#56353

Change-Id: I985e1553e7b02237403b833e96fb5ceec890f5b8
GitHub-Last-Rev: 96a35e5
GitHub-Pull-Request: golang#60929
Reviewed-on: https://go-review.googlesource.com/c/go/+/504882
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
  • Loading branch information
earthboundkid authored and eric committed Sep 7, 2023
1 parent ddcd0ee commit 2fd1951
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions api/next/56353.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg slices, func Concat[$0 interface{ ~[]$1 }, $1 interface{}](...$0) $0 #56353
16 changes: 16 additions & 0 deletions src/slices/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,19 @@ func Reverse[S ~[]E, E any](s S) {
s[i], s[j] = s[j], s[i]
}
}

// Concat returns a new slice concatenating the passed in slices.
func Concat[S ~[]E, E any](slices ...S) S {
size := 0
for _, s := range slices {
size += len(s)
if size < 0 {
panic("len out of range")
}
}
newslice := Grow[S](nil, size)
for _, s := range slices {
newslice = append(newslice, s...)
}
return newslice
}
98 changes: 98 additions & 0 deletions src/slices/slices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1073,3 +1073,101 @@ func TestInference(t *testing.T) {
t.Errorf("Reverse(%v) = %v, want %v", S{4, 5, 6}, s2, want)
}
}

func TestConcat(t *testing.T) {
cases := []struct {
s [][]int
want []int
}{
{
s: [][]int{nil},
want: nil,
},
{
s: [][]int{{1}},
want: []int{1},
},
{
s: [][]int{{1}, {2}},
want: []int{1, 2},
},
{
s: [][]int{{1}, nil, {2}},
want: []int{1, 2},
},
}
for _, tc := range cases {
got := Concat(tc.s...)
if !Equal(tc.want, got) {
t.Errorf("Concat(%v) = %v, want %v", tc.s, got, tc.want)
}
var sink []int
allocs := testing.AllocsPerRun(5, func() {
sink = Concat(tc.s...)
})
_ = sink
if allocs > 1 {
errorf := t.Errorf
if testenv.OptimizationOff() || race.Enabled {
errorf = t.Logf
}
errorf("Concat(%v) allocated %v times; want 1", tc.s, allocs)
}
}
}

func TestConcat_too_large(t *testing.T) {
// Use zero length element to minimize memory in testing
type void struct{}
cases := []struct {
lengths []int
shouldPanic bool
}{
{
lengths: []int{0, 0},
shouldPanic: false,
},
{
lengths: []int{math.MaxInt, 0},
shouldPanic: false,
},
{
lengths: []int{0, math.MaxInt},
shouldPanic: false,
},
{
lengths: []int{math.MaxInt - 1, 1},
shouldPanic: false,
},
{
lengths: []int{math.MaxInt - 1, 1, 1},
shouldPanic: true,
},
{
lengths: []int{math.MaxInt, 1},
shouldPanic: true,
},
{
lengths: []int{math.MaxInt, math.MaxInt},
shouldPanic: true,
},
}
for _, tc := range cases {
var r any
ss := make([][]void, 0, len(tc.lengths))
for _, l := range tc.lengths {
s := make([]void, l)
ss = append(ss, s)
}
func() {
defer func() {
r = recover()
}()
_ = Concat(ss...)
}()
if didPanic := r != nil; didPanic != tc.shouldPanic {
t.Errorf("slices.Concat(lens(%v)) got panic == %v",
tc.lengths, didPanic)
}
}
}

0 comments on commit 2fd1951

Please sign in to comment.