'Why does append modify passed slice

How could I iterate through the slice and pass somewhere the slice except the current element? Seems append() function modifies the underlying slice as we could see in documentation. But anyway I still don't know how to reach this.

func main() {
    args := []string{ "2", "3", "8" }

    for i, _ := range args {
        fmt.Println(append(args[:i], args[i+1:]...)) // or pass to function
    }

    fmt.Println(args)
}

result:

[3 8]
[3 8]
[3 8]
[3 8 8] // it is args now

what I expect:

 [3 8]
 [2 8]
 [2 3]

I already saw this Why does append() modify the provided slice? (See example)

but what is the Capacity of the slice is the secret for me, and I dont understand why did I exceed it.

go


Solution 1:[1]

Performance is the big reason. Creating a new slice and copying all the elements over to it is expensive, so the slice code doesn't copy without good reason. However, if you exceed the slice's capacity, it grows by a suitable amount by copying the underlying slice. That means that the slice that's returned from append may not be the same slice you passed in.

The preferred way to use is:

args = append(args, newarg)

If you take a subslice, the capacity stays the same but your view into the slice changes. That means the missing elements are still there but outside the bounds of the new slice.

This explains the odd output of your code. You're printing the result of append each time but not storing that result, which means args isn't the same as what you printed.

The initial args slice is 3 elements big. For each index i - which is to say for 0, 1 and 2 - you take a subslice args[:i] and append all the elements of the remainder of the array args[i+1:] to it. That means that for:

i    args[:i]     args[i+1:]...   Result         args
0    {}           {"3", "8"}     {"3", "8"}     {"3", "8", "8"}
1    {"3"}        {"8"}          {"3", "8"}     {"3", "8", "8"}
2    {"3", "8"}   {}             {"3", "8"}     {"3", "8", "8"}

tl;dr you should always save the result of append, and if you want to make a copy so you can change it then make a copy yourself.

Solution 2:[2]

Append always tries to modify the underlying array.

Lets look at the first execution of the loop

append(args[:0], args[0+1:]...)

What this does is append the slice {3,8} to the slice {} since args[:0] gives you an empty slice which ends at the start of the array. Thats why your array comes out as [3 8 8]because 3 8 gets appended to the array. Read more about this on the wiki.

You can set a default capacity using make i.e.

args := make([]string, 0, CAPACITY)

You can also check the capacity for a slice

a := []int{1,2,3}
fmt.Println(cap(a))
>>> 3

Finally if you don't want to recopy the array every time as in Elwinar's answer I would recommend passing in the two slices, a[:i] and a[i+1:], to the function.

Solution 3:[3]

The Go Programming Language Specification

Appending to and copying slices

The built-in functions append and copy assist in common slice operations. For both functions, the result is independent of whether the memory referenced by the arguments overlaps.

The variadic function append appends zero or more values x to s of type S, which must be a slice type, and returns the resulting slice, also of type S. The values x are passed to a parameter of type ...T where T is the element type of S and the respective parameter passing rules apply. As a special case, append also accepts a first argument assignable to type []byte with a second argument of string type followed by .... This form appends the bytes of the string.

append(s S, x ...T) S  // T is the element type of S

If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.

s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }

The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. Both arguments must have identical element type T and must be assignable to a slice of type []T. The number of elements copied is the minimum of len(src) and len(dst). As a special case, copy also accepts a destination argument assignable to type []byte with a source argument of a string type. This form copies the bytes from the string into the byte slice.

copy(dst, src []T) int
copy(dst []byte, src string) int

Examples:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

Don't overwrite your input by using append. Use a separate variable for your output (function argument). For example,

package main

import "fmt"

func main() {
    args := []string{"2", "3", "8"}
    fmt.Println(args)
    funcArg := make([]string, len(args)-1)
    for i := range args {
        copy(funcArg, args[:i])
        copy(funcArg[i:], args[i+1:])
        fmt.Println(funcArg)
    }
    fmt.Println(args)
}

Output:

[2 3 8]
[3 8]
[2 8]
[2 3]
[2 3 8]

Solution 4:[4]

You can imagine a slice as a struct composed of an array of fixed size and a counter for the number of elements in it. The capacity of the slice is the size of the underlying array, while the lenght of the slice is the counter.

Append is defined that way : func append(slice []Type, elems ...Type) []Type (godoc), which essentially means that you will be appending the elem variadic argument to the slice argument. If len(elems) + len(slice) > cap(slice), then udnerlying array will need to be changed for a bigger one (with a bigger capacity), which mean (in go) a new slice (hence the return parameter).

In your case, you didn't exceeded the capacity of the slice. You just modified its content.

One simple (albeit slightly ugly) trick would be to nested two appends to an empty slice :

package main

import "fmt"

func main() {
    args := []string{ "2", "3", "8" }

    for i, _ := range args {
        fmt.Println(append(append([]string{}, args[:i]...), args[i+1:]...)) 
    }

    fmt.Println(args)
}

Or, if you want to pass a copy of the slice to a method (and do what you want after that), you can use the copy function…

Solution 5:[5]

The existing answers adequately explain the behaviour observed by the OP and provide viable solutions. However, none mention full slice expressions (a.k.a. three-index slices) introduced in Go 1.2 (late 2012). Using one as the first argument of append solves the OP's problem in a concise manner. I'm including the approach here for completeness:

package main

import "fmt"

func main() {
    args := []string{"2", "3", "8"}
    for i, _ := range args {
        fmt.Println(append(args[:i:i], args[i+1:]...))
    }
    fmt.Println(args)
}

(Playground)

Output:

[3 8]
[2 8]
[2 3]
[2 3 8]

Since Go 1.18, you can use functions Clone and Delete from the golang.org/x/exp/slices package to achieve the same result in an arguably more readable fashion:

package main

import (
    "fmt"

    "golang.org/x/exp/slices"
)

func main() {
    args := []string{"2", "3", "8"}
    for i, _ := range args {
        fmt.Println(slices.Delete(slices.Clone(args), i, i+1))
    }
    fmt.Println(args)
}

(Playground)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 River
Solution 2 Benjamin Kadish
Solution 3
Solution 4 Elwinar
Solution 5