Insert a value in a slice at a given index

I found the question setup pretty tricky to follow.

Rephrased, they want to insert an element. Here we have an array where it's missing the element 3 and we want to insert it.

package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 4, 5, 6}
    b := 3

    // Make space in the array for a new element. You can assign it any value.
    a = append(a, 0)   
    fmt.Println(a)

    // Copy over elements sourced from index 2, into elements starting at index 3.
    copy(a[3:], a[2:])  
    fmt.Println(a)

    a[2] = b         
    fmt.Println(a)
}

Simple, efficient and logical way:

  1. Make sure array1 has enough capacity (length) to accomodate the new, insertable element. To do that, append a single element using the builting append() (doesn't matter what that is, it'll get overwritten).
  2. To insert an element, existing elements must be shifted (copied over to 1 index higher) to make room for that element, e.g. using the builtin copy() (elements you want to insert before).
  3. Set the element at the proper index, using a single assignment.

In code:

array1 := []int{1, 3, 4, 5}
array2 := []int{2, 4, 6, 8}

array1 = append(array1, 0)   // Step 1
copy(array1[2:], array1[1:]) // Step 2
array1[1] = array2[2]        // Step 3

fmt.Println(array1)

Output (try it on the Go Playground):

[1 6 3 4 5]

Optimization in special cases

Note that in some special cases (when the slice element is big, like a big struct), it may be faster to append the last element, and then it's enough to copy 1 less elements (because the appended last element is right where it needs to be).

This is how it looks like:

last := len(array1) - 1
array1 = append(array1, array1[last]) // Step 1
copy(array1[2:], array1[1:last])      // Step 2
array1[1] = array2[2]                 // Step 3

This will result in the same slice. Try this one on the Go Playground.


A simple append is what you need:

a = append(a[:index+1], a[index:]...)
a[index] = value

Note: len(a) > 0 && index < len(a)

Should len(a) == index, meaning nil or empty slice or append after the last element:

a = append(a, value)

Inserting at the index zero for slice of ints:

a = append([]int{value}, a...)

All in one function:

// 0 <= index <= len(a)
func insert(a []int, index int, value int) []int {
    if len(a) == index { // nil or empty slice or after last element
        return append(a, value)
    }
    a = append(a[:index+1], a[index:]...) // index < len(a)
    a[index] = value
    return a
}

Usage:

    a := []int{10, 30, 40}
    a = insert(a, 1, 20)
    fmt.Println(a) // [10 20 30 40]

And for the OP:

    slice1 := []int{1, 3, 4, 5}
    slice2 := []int{2, 4, 6, 8}
    // slice1 = insert(slice1, 1, slice2[2])
    slice1 = append(slice1[:2], slice1[1:]...)
    slice1[1] = slice2[2]

    fmt.Println(slice1) // [1 6 3 4 5]

Benchmark:

go version
# go version go1.16.3 linux/amd64
make bench
go test -benchmem -bench . -args -n 32
# BenchmarkInsert-8      4125085  275.0 ns/op  512 B/op  1 allocs/op
# BenchmarkInsert2-8     3778551  316.0 ns/op  512 B/op  1 allocs/op

go test -benchmem -bench . -args -n 1000
# BenchmarkInsert-8       198364  5876 ns/op  16384 B/op  1 allocs/op
# BenchmarkInsert2-8      205197  7123 ns/op  16384 B/op  1 allocs/op

go test -benchmem -bench . -args -n 1000000
# BenchmarkInsert-8          643  1898436 ns/op 10002437 B/op  1 allocs/op
# BenchmarkInsert2-8         368  3248385 ns/op 10002436 B/op  1 allocs/op

Code:

func insert(a []int, index int, value int) []int {
    a = append(a[:index+1], a[index:]...) // Step 1+2
    a[index] = value                      // Step 3
    return a
}
func insert2(a []int, index int, value int) []int {
    last := len(a) - 1
    a = append(a, a[last])           // Step 1
    copy(a[index+1:], a[index:last]) // Step 2
    a[index] = value                 // Step 3
    return a
}
func BenchmarkInsert(b *testing.B) {
    for i := 0; i < b.N; i++ {
        r = insert(a, 2, 42)
    }
}
func BenchmarkInsert2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        r = insert2(a, 2, 42)
    }
}

var (
    n    = flag.Int("n", 32, "buffer length")
    a, r []int
)

// We use TestMain to set up the buffer.
func TestMain(m *testing.M) {
    flag.Parse()
    a = make([]int, *n)
    os.Exit(m.Run())
}

You may combine the two first steps to one; by using:

    a = append(a[:index+1], a[index:]...)
  1. This makes sure the array has enough capacity to accommodate the new element.
  2. This copies all required elements to one index higher to make room for the new element.
  3. Set the element at the index, using a single assignment: a[index] = value

Which is more efficient, according to the benchmarks.

Tags:

Slice

Go