This question already has answers here:
Slice chunking in Go
(8 answers)
Closed 8 months ago.
I'm recently using Go to create applications. My question is this: at a certain point in the program I have a string slice:
my_slice = []string{"string1","string2","string3","string4","string5","string6"}
It contains 6 strings. Which is the best procedure (easier to write and understand) to break this slice into 3 parts, for example, and distribute the content in three sub-slices?
If I have:
var my_sub_slice1 []string
var my_sub_slice2 []string
var my_sub_slice3 []string
I wish at the end of it my_sub_slice1 will contain (in his first two entries) "string1","string2"; my_sub_slice2 will contain (in his first two entries) "string3","string4"; my_sub_slice3 will contain (in his first two entries) "string5","string6".
I know the question is easy but I haven't found a clean and efficient way to do this yet.
Thanks a lot!
A general solution to split a slice to into sub-slices of equal length
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
size := 2
var j int
for i := 0; i < len(s); i += size{
j += size
if j > len(s) {
j = len(s)
}
// do what do you want to with the sub-slice, here just printing the sub-slices
fmt.Println(s[i:j])
}
// output
// [1 2]
// [3 4]
// [5 6]
// [7 8]
// [9 10]
my_sub_slice1 := my_slice[0:2]
my_sub_slice2 := my_slice[2:4]
my_sub_slice3 := my_slice[4:6]
Related
I have a simple array with values [1, 2, 3], and I'd like to find all permutations. I don't understand why moving 'copying' part of the code before the loop breaks the program.
func generatePermutations(curr, remains []int) [][]int {
if len(remains) == 0 {
return [][]int{curr}
}
var res [][]int
// DOESN'T WORK
c, r := make([]int, len(curr)), make([]int, len(remains))
copy(c, curr)
copy(r, remains)
for i := 0; i < len(remains); i++ {
// WORKS
//c, r := make([]int, len(curr)), make([]int, len(remains))
//copy(c, curr)
//copy(r, remains)
curr = append(curr, remains[i])
res = append(res, generatePermutations(curr, append(append(remains[:i]), remains[i+1:]...))...)
curr = c
remains = r
}
return res
}
When copy is outside the loop the result is the following:
[[1 2 3] [1 3 2] [2 1 3] [2 3 1] [3 3 3] [3 3 3]]
When copy is inside the loop the result is the following:
[[1 2 3] [1 3 2] [2 1 3] [2 3 1] [3 1 2] [3 2 1]]
In the first output there are two arrays with [3,3,3] which is wrong
You say that I neither modify "c" or "r" nor append to them, which is partially true.
In the first iteration of the loop,
the slices c and curr point to different backing arrays, so this is fine.
But when you do
curr = c
a bit later, you're actually assigning both slices to point to the same backing array.
This means that on the second iteration, your append could modify both c and curr ("could" because a resize would change the backing array for curr).
This is what's causing the strange behavior you see above.
Slices in go are a bit tricky, so when you know you'll be mutating and passing them around, it's better to avoid assignments, but rather stick to copying them entirely (as you do in the "WORKS" case).
For further reading this is a nice resource: https://go.dev/blog/slices-intro
I am fairly new to go and this example has had me baffled:
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
output:
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
why does the capacity change to 4 in the last one? capacity by definition is the length of the underlying array.
Slice is built on arrays, it consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
The key point is segment, not the array.
s = s[2:]
when you drop the first two values, s became a slice pointed to the other half segment of the array, starting from the third element, so that it's cap is 4.
just like you can not do an s[-1] to point to the previous part of the array, that part can not be count into the cap.
ref: https://blog.golang.org/slices-intro
The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
In your example, in the last case starting element of the slice is 5 and in the underlying arrray({2, 3, 5, 7, 11, 13}) poistion of 5 is 2. To count the capacity you should start counting from index 2. If you count from that position you will get the correct capacity 4
package main
import (
"fmt"
)
func main() {
result := subsets([]int{9,3,0,1,2})
fmt.Println(result)
}
func subsets(nums []int) [][]int {
result := [][]int{}
var fun func([]int, int)
fun = func (preSets []int, start int) {
// fmt.Println(start, preSets, result)
result = append(result, preSets)
// fmt.Println(result)
for idx := start; idx < len(nums); idx++ {
tmp := nums[idx]
newSet := append(preSets, tmp)
fun(newSet, idx+1)
newSet = newSet[:len(newSet) - 1]
}
}
fun([]int{}, 0)
return result
}
i want to find the subsets of a slice, and think the code above should work. but it give me the following output
[[] [9] [9 3] [9 3 0] [9 3 0 2] [9 3 0 1 2] [9 3 0 2] [9 3 1] [9 3 1 2] [9 3 2] [9 0] [9 0 1] [9 0 1 2] [9 0 2] [9 1] [9 1 2] [9 2] [3] [3 0] [3 0 1] [3 0 1 2] [3 0 2] [3 1] [3 1 2] [3 2] [0] [0 1] [0 1 2] [0 2] [1] [1 2] [2]]
the fifth slice should be [9 3 0 1], but it is [9 3 0 2],and i print the result each step, i found out that the fifth slice turned from [9301] to [9302] when the seventh slice [9302] is appended,i think it should be related to the array storage under the slice, but why
i think [the problem is] related to the array storage under the slice
Yes, it is.
but why
The append function:
sometimes re-uses the original backing array, but
sometimes creates a new backing array and copies the original values to the new array.
There are no promises made about when it does one of these two.
When it re-uses the original array, you encounter the behavior you dislike. When it makes a new array, you encounter the behavior you desire. Since you want the copy behavior, you can simply write your own "always copy" code and get the behavior you want. However, a more-minimal change is to replace:
result = append(result, preSets)
with:
result = append(result, unshare(preSets))
with the function unshared defined as:
func unshare(a []int) []int {
tmp := make([]int, len(a))
copy(tmp, a)
return tmp
}
If you are trying to explain to yourself exactly why you got the exact result that you got, it's a little tricky because you're not promised anything about when append makes a new backing array and when it re-uses the existing backing array—but it is explainable, if you make the following two assumptions:
append never makes a copy of the backing array if it does not have to; and
when append does make a copy, it sometimes over-allocates the new backing array by some factor.
That is, append behaves more or less like this, except of course this one is specific to []int:
func xappend(orig []int, add []int) []int {
has := len(orig)
needed := has + len(add)
// If we can fit the added elements in, do that now.
if cap(orig) >= needed {
fmt.Println("copy in place")
orig = orig[:needed]
copy(orig[has:needed], add)
return orig
}
newSize := undocumentedFunction(has, cap(orig), needed)
fmt.Println("make new, size", newSize)
new := make([]int, needed, newSize)
copy(new, orig)
copy(new[has:needed], add)
return new
}
func undocumentedFunction(oldCap, oldLen, newCap int) int {
twiceAsMuch := oldCap + oldCap
if newCap > twiceAsMuch {
return newCap
}
// 2x old capacity is enough, but
// we'll tweak this in various ways.
// The exact tweaks might change at any time.
if oldLen < 1024 {
return twiceAsMuch
}
panic("didn't copy this part")
}
This "undocumented function" is part of the runtime system, and it collaborates with the compiler. You can see its actual code here (note: this link may break or go to the wrong line at some point).
(Sample runnable code here on the Go Playground.)
I'm going through the basics of go, found this in the tour here.
I don't understand why the array values are not 0 or nil after s = s[:0]
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
output :
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
A slice is a view on an array. A slice will only create a new larger array and copy contents if the capacity is exceeded. A slice will continue using its original array if you truncate it. When you create a smaller slice, it is still using the old backing array with the contents leftover from the previous operations. When you append to that slice, it will continue using the same array until you exceed capacity.
I've recently been messing around with Go and I wanted to see how it would be to delete an element from a two-dimensional slice.
For deleting an element from a one-dimensional slice, I can successfully use:
data = append(data[:i], data[i+1:]...)
However, with a two-dimensional slice, using:
data = append(data[i][:j], data[i][j+1:]...)
throws the error:
cannot use append(data[i][:j], data[i][j+1:]...) (type []string) as type [][]string in assignment
Would tackling this require a different approach?
A 2D slice in Go is nothing more than a slice of slices. So if you want to remove an element from this 2D slice, effectively you still only have to remove an element from a slice (which is an element of another slice).
There is nothing more involved. Only thing you have to look out is that when you remove an element from the row-slice, the result will only be the "new" value of the row (an element) of the "outer" slice, and not the 2D slice itself. So you have to assign the result to an element of the outer slice, to the row whose element you just removed:
// Remove element at the ith row and jth column:
s[i] = append(s[i][:j], s[i][j+1:]...)
Note that this is identical to the simple "removal from slice" if we substitute s[i] with a (not surprisingly, because s[i] denotes the "row-slice" whose jth element we're removing):
a = append(a[:j], a[j+1:]...)
See this complete example:
s := [][]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
}
fmt.Println(s)
// Delete element s[1][2] (which is 6)
i, j := 1, 2
s[i] = append(s[i][:j], s[i][j+1:]...)
fmt.Println(s)
Output (try it on the Go Playground):
[[0 1 2 3] [4 5 6 7] [8 9 10 11]]
[[0 1 2 3] [4 5 7] [8 9 10 11]]
Here is one of the possible approaches Go Playground.
b := [][]int{
[]int{1, 2, 3, 4},
[]int{5, 6, 7, 8},
[]int{9, 0, -1, -2},
[]int{-3, -4, -5, -6},
}
print2D(b)
i, j := 2, 2
tmp := append(b[i][:j], b[i][j+1:]...)
c := append(b[:i], tmp)
c = append(c, b[i+1:]...)
print2D(c)
Basically I am extracting the i-th row, remove the element from it append(b[i][:j], b[i][j+1:]...) and then put this row between the rows.
If someone would tell how to append many elements, it would look even nicer.