Jon Chan
Jon Chan

Reputation: 979

Removing each element in a slice

I'm getting some unexpected behavior when I try to loop through a slice and remove every element in sequence to print the remaining elements, using the suggested Delete method from SliceTricks. For example, when I try and loop through a slice that contains the letters [A B C], I expect the output to be [B C], [A C], [A B] in that order:

Method 1

package main

import "fmt"

func main() {
    a := []string {"A", "B", "C"}
    for i, _ := range a {
        fmt.Println(append(a[:i], a[i+1:]...))
    }
}

However, the output here is surprising to me. It outputs [B C] three times.

I did eventually get my expected behavior by doing the following:

Method 2

package main

import "fmt"

func main() {
    a := []string {"A", "B", "C"}
    for i, _ := range a {
        result := make([]string, 0)
        result = append(result, a[:i]...)
        result = append(result, a[i+1:]...)
        fmt.Println(result)
    }
}

What is causing the unexpected behavior in method 1 and is there a better way to achieve this other than method 2?

Upvotes: 2

Views: 201

Answers (3)

jimt
jimt

Reputation: 26370

If you want to do it without extra allocations, try just swapping out values as shown in this example:

Note that this assumes you are OK with shuffling the list contents. If not, just copy the list once before the loop and do the same as below.

package main

import "fmt"

func main() {
    set := []string{"a", "b", "c"}
    for i := range set {
        set[0], set[i] = set[i], set[0]
        fmt.Println(set[1:])
    }
}

Output:

[b c]
[a c]
[a b]

Upvotes: 5

lsowen
lsowen

Reputation: 3828

The "what" is because append(slice, elems...) is actually updating the slice with the new elements. It returns a "new" slice only because it may have had to relocate the original slice due to memory reallocation. So, in your example code, you are actually changing the contents of a with each call to append. (A good overview is in the "Append: An Example" section of this golang blog post).

As to "how", my attempt is:

package main

import "fmt"

func main() {
    a := []string {"A", "B", "C"}
    for i, _ := range a {
        result := make([]string, i, len(a)-1)
        copy(result, a[:i])
        result = append(result, a[i+1:]...)
        fmt.Println(result)
    }
}

DEMO

Initializing result with a length and capacity, as well as using copy instead of two appends attempts to reduce the number of memory reallocations required.

Upvotes: 1

topskip
topskip

Reputation: 17335

See what happens after the first iteration:

package main

import "fmt"

func main() {
    a := []string{"A", "B", "C"}
    x := append(a[:0], a[1:]...)
    fmt.Println(a)
    fmt.Println(x)
}

Gives

[B C C]
[B C]

See https://play.golang.org/p/2LqBwHejhy

The next two iterations are similar. Just try it out.

Upvotes: 0

Related Questions