GOLANG SLICE

  • A slice is a data structure describing a contiguous section of an array stored separately from the slice variable itself. 
  • A slice is not an array.
  • A slice describes a piece of an array.

Golang Slice Example

Given our buffer array variable from the previous section, we could create a slice that describes elements 100 through 150 (to be precise, 100 through 149, inclusive) by slicing the array:

var slice []byte = buffer[100:150]
//OR 
var slice = buffer[100:150]
//OR
slice := buffer[100:150]

// Strucy look something like this
type sliceHeader struct {
    Length        int
    Capacity      int
    ZerothElement *byte
}

slice := sliceHeader{
    Length:        50,
    Capacity:      200,
    ZerothElement: &buffer[100],
}

Slice Assignments

Empty slice

  • capSlice := iBuffer[0:0]
func main() {
	var iBuffer [10]int

	fmt.Printf("Before: cap(iBuffer)=%d \nlen(iBuffer)=%d\n", cap(iBuffer), len(iBuffer))
	fmt.Printf("iBuffer=%#v\n", iBuffer)

	// This is important
	// This is empty slice
	capSlice := iBuffer[0:0]

	fmt.Printf("\nAFTER: cap(capSlice)=%d \nlen(capSlice)=%d\n", cap(capSlice), len(capSlice))
	fmt.Printf("capSlice=%#v\n", capSlice)
}

******************************
Output
******************************
Before: cap(iBuffer)=10 
len(iBuffer)=10
iBuffer=[10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

AFTER: cap(capSlice)=10 
len(capSlice)=0
capSlice=[]int{}

Copy of Slice

  • capSlice := iBuffer[:]
func main() {
	var iBuffer [10]int

	fmt.Printf("Before: cap(iBuffer)=%d \nlen(iBuffer)=%d\n", cap(iBuffer), len(iBuffer))
	fmt.Printf("iBuffer=%#v\n", iBuffer)

	// This is important
	// This is copy of original slice
	capSlice := iBuffer[:]

	fmt.Printf("\nAFTER: cap(capSlice)=%d \nlen(capSlice)=%d\n", cap(capSlice), len(capSlice))
	fmt.Printf("capSlice=%#v\n", capSlice)
}

******************************
Output
******************************
Before: cap(iBuffer)=10 
len(iBuffer)=10
iBuffer=[10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

AFTER: cap(capSlice)=10 
len(capSlice)=10
capSlice=[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

Passing slices to functions

Changing underlying array values

  • Below code will change the values of original array even though slice input is copy of the slice header.
  • It contains address of underline array.
package main

import "fmt"

func AddOneToEachElement(slice []byte) {
	for i := range slice {
		slice[i]++
	}
}

func main() {
	var buffer [50]byte
	slice := buffer[10:20]
	for i := 0; i < len(slice); i++ {
		slice[i] = byte(i)
	}
	fmt.Println("before", slice)
	AddOneToEachElement(slice)
	fmt.Println("after", slice)
}

******************************
Output
******************************
before [0 1 2 3 4 5 6 7 8 9]
after [1 2 3 4 5 6 7 8 9 10]

Program exited.

Modifying Slice itself

package main

import "fmt"

func SubtractOneFromLength(slice []byte) []byte {
	slice = slice[0 : len(slice)-1]
	return slice
}

func main() {
	var buffer [50]byte
	slice := buffer[10:20]
	fmt.Println("Before: len(slice) =", len(slice))
	
	// We have to save updated slice
	newSlice := SubtractOneFromLength(slice)
	fmt.Println("After:  len(slice) =", len(slice))
	fmt.Println("After:  len(newSlice) =", len(newSlice))
}

******************************
Output
******************************
Before: len(slice) = 10
After:  len(slice) = 10
After:  len(newSlice) = 9
Program exited.

Pointers to slices: Method receivers

Version-1 : Using Pointer

package main

import "fmt"

func PtrSubtractOneFromLength(slice *[]byte) {
	*slice = (*slice)[0 : len(*slice)-1]
}

func main() {
	var buffer [50]byte
	slice := buffer[10:20]
	fmt.Println("Before: len(slice) =", len(slice))

	// passed by reference
	PtrSubtractOneFromLength(&slice)
	fmt.Println("After:  len(slice) =", len(slice))
}

******************************
Output
******************************
Before: len(slice) = 10
After:  len(slice) = 9

Version-2 : Using Custom Type

package main

import "fmt"

type customType []byte

func (fp *customType) TruncationLastByte() {
	*fp = (*fp)[0 : len(*fp)-1]
}

func main() {

	buffer := customType("robertPike")
	fmt.Println("Before: len(slice) =", len(buffer))

	// passed using custom type
	buffer.TruncationLastByte()

	fmt.Println("After:  len(slice) =", len(buffer))
}

******************************
Output
******************************
Before: len(slice) = 10
After:  len(slice) = 9

Capacity

Full Slice Check

if cap(slice) == len(slice) {
    fmt.Println("slice is full!")
}

Verification of capacity working

package main

import "fmt"

func main() {
	var iBuffer [10]int

	fmt.Printf("Before: cap(iBuffer)=%d \nlen(iBuffer)=%d\n", cap(iBuffer), len(iBuffer))
	fmt.Printf("iBuffer=%#v\n", iBuffer)

	// This is important
	// This is empty slice
	capSlice := iBuffer[0:0]

	fmt.Printf("\nAFTER: cap(capSlice)=%d \nlen(capSlice)=%d\n", cap(capSlice), len(capSlice))
	fmt.Printf("capSlice=%#v\n", capSlice)

	for i := 0; i < 20; i++ {
		capSlice = Extend(capSlice, i)
		fmt.Println(capSlice)
	}

}

func Extend(inputSlice []int, newElement int) []int {
	n := len(inputSlice)
	// *****************************
	// At below line panic will occur
	inputSlice = inputSlice[:n+1]
	inputSlice[len(inputSlice)-1] = newElement
	return inputSlice
}

******************************
Output
******************************
Before: cap(iBuffer)=10 
len(iBuffer)=10
iBuffer=[10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

AFTER: cap(capSlice)=10 
len(capSlice)=0
capSlice=[]int{}
[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6]
[0 1 2 3 4 5 6 7]
[0 1 2 3 4 5 6 7 8]
[0 1 2 3 4 5 6 7 8 9]
panic: runtime error: slice bounds out of range [:11] with capacity 10

goroutine 1 [running]:
main.Extend(...)
        /Users/john.doe/go/src/goexample/slice/capacity.go:29
main.main()
        /Users/john.doe/go/src/goexample/slice/capacity.go:19 +0x278
exit status 2

Grow Slice beyond capacity

Use Make

package main

import "fmt"

func main() {
	var arr []int = make([]int, 5, 10)
	printArr(arr)

	// version-1 new slice with double capacity using make
	newSlice := make([]int, 2*cap(arr))

	// go compiler suggestion as below
	// should use copy(to, from) instead of a loop (S1001)go-staticcheck
	for i := range arr {
		newSlice[i] = arr[i]
	}

	printArr(newSlice)
}

func printArr(arr []int) {
	fmt.Printf("\nlen(arr)=%d cap(arr)=%d\n", len(arr), cap(arr))
	fmt.Println("arr=", arr)
}

******************************
Output
******************************
len(arr)=5 cap(arr)=10
arr= [0 0 0 0 0]

len(arr)=20 cap(arr)=20
arr= [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

Using Copy

  • The copy function is smart.
  • It only copies what it can, paying attention to the lengths of both arguments.
  • In other words, the number of elements it copies is the minimum of the lengths of the two slices.
package main

import "fmt"

func main() {
	var arr []int = make([]int, 5, 10)
	printSlice(arr)

	newSlice := make([]int, 2*cap(arr))

	// version-2 new slice with double capacity using make
	// using copy
	copy(newSlice, arr)

	printSlice(newSlice)
}

func printSlice(arr []int) {
	fmt.Printf("\nlen(arr)=%d cap(arr)=%d\n", len(arr), cap(arr))
	fmt.Println("arr=", arr)
}

******************************
Output
******************************
len(arr)=5 cap(arr)=10
arr= [0 0 0 0 0]

len(arr)=20 cap(arr)=20
arr= [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

Use case

Inserts the value into the slice at the specified index

Version-1: Creating new slice
package main

import "fmt"

func main() {
	input := []int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}

	// case-1: Creating new slice
	arr := input
	fmt.Println("\nInitialization")
	printSlice(arr)

	// // case-2: Extending existing slice by 1
	// arr := make([]int, 15, 20)
	// fmt.Println("Original")
	// printSlice(arr)

	// copy(arr[0:], input)
	// fmt.Println("\nInitialization")
	// printSlice(arr)

	fmt.Println("\nBefore")
	printSlice(arr)

	arr = insertAtIndex(arr, 5, 28)

	fmt.Println("\nAfter")
	printSlice(arr)
}

func insertAtIndex(arr []int, index int, newElement int) []int {

	if index < 0 || index > len(arr) {
		fmt.Println("Index is out of bound")
		return arr
	}
	newSlice := arr[:]
	if len(arr) == cap(arr) {
		// Slice is full; must grow.
		// We increase its size by 1, so if the size is zero we still grow.
		fmt.Println("\nCreating new slice")
		newSlice = make([]int, len(arr)+1, 2*cap(arr))
		copy(newSlice, arr)
	} else {
		fmt.Println("\nExtending existing slice by 1")
		newSlice = arr[:len(arr)+1]
	}

	copy(newSlice[index+1:], newSlice[index:])

	newSlice[index] = newElement
	return newSlice
}

func printSlice(arr []int) {
	fmt.Printf("len(arr)=%d cap(arr)=%d\n", len(arr), cap(arr))
	fmt.Println("arr=", arr)
}

******************************
Output
******************************
Initialization
len(arr)=10 cap(arr)=10
arr= [11 12 13 14 15 16 17 18 19 20]

Before
len(arr)=10 cap(arr)=10
arr= [11 12 13 14 15 16 17 18 19 20]

Creating new slice

After
len(arr)=11 cap(arr)=20
arr= [11 12 13 14 15 28 16 17 18 19 20]
Version-2: Extending existing slice by 1
package main

import "fmt"

func main() {
	input := []int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}

	// // case-1: Creating new slice
	// arr := input
	// fmt.Println("\nInitialization")
	// printSlice(arr)

	// case-2: Extending existing slice by 1
	arr := make([]int, 15, 20)
	fmt.Println("Original")
	printSlice(arr)

	copy(arr[0:], input)
	fmt.Println("\nInitialization")
	printSlice(arr)

	fmt.Println("\nBefore")
	printSlice(arr)

	arr = insertAtIndex(arr, 5, 28)

	fmt.Println("\nAfter")
	printSlice(arr)
}

func insertAtIndex(arr []int, index int, newElement int) []int {

	if index < 0 || index > len(arr) {
		fmt.Println("Index is out of bound")
		return arr
	}
	newSlice := arr[:]
	if len(arr) == cap(arr) {
		// Slice is full; must grow.
		// We increase its size by 1, so if the size is zero we still grow.
		fmt.Println("\nCreating new slice")
		newSlice = make([]int, len(arr)+1, 2*cap(arr))
		copy(newSlice, arr)
	} else {
		fmt.Println("\nExtending existing slice by 1")
		newSlice = arr[:len(arr)+1]
	}

	copy(newSlice[index+1:], newSlice[index:])

	newSlice[index] = newElement
	return newSlice
}

func printSlice(arr []int) {
	fmt.Printf("len(arr)=%d cap(arr)=%d\n", len(arr), cap(arr))
	fmt.Println("arr=", arr)
}


******************************
Output
******************************
Original
len(arr)=15 cap(arr)=20
arr= [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

Initialization
len(arr)=15 cap(arr)=20
arr= [11 12 13 14 15 16 17 18 19 20 0 0 0 0 0]

Before
len(arr)=15 cap(arr)=20
arr= [11 12 13 14 15 16 17 18 19 20 0 0 0 0 0]

Extending existing slice by 1

After
len(arr)=16 cap(arr)=20
arr= [11 12 13 14 15 28 16 17 18 19 20 0 0 0 0 0]

Append

package main

import "fmt"

func main() {
	slice1 := []int{11, 12, 13}
	slice2 := []int{14, 15, 16}

	printSlice(slice1)
	printSlice(slice2)

	// slice1 = appendV1(slice1, slice2...)
	slice1 = appendV2(slice1, slice2...)
	printSlice(slice1)

	slice1 = appendV2(slice1, 100)
	printSlice(slice1)

	slice1 = appendV2(slice1, 101, 102)
	printSlice(slice1)
}

func appendV2(slice []int, elements ...int) []int {
	// efficient version
	n := len(slice)
	total := len(slice) + len(elements)
	if total > cap(slice) {
		newSlice := make([]int, total, 2*total+1)
		copy(newSlice, slice)
		slice = newSlice
	}

	slice = slice[:total]
	copy(slice[n:], elements)

	return slice
}

func appendV1(input []int, items ...int) []int {
	// Simpler version

	total := len(input) + len(items)
	var resultSlice []int

	if total > cap(input) {
		fmt.Println("Creating new slice")
		resultSlice = make([]int, total, 2*total+1)
		copy(resultSlice, input)
	} else {
		resultSlice = input[:total]
	}

	copy(resultSlice[len(input):], items)
	return resultSlice
}

func printSlice(arr []int) {
	fmt.Printf("len(arr)=%d cap(arr)=%d\n", len(arr), cap(arr))
	fmt.Println("arr=", arr)
}

******************************
Output
******************************
len(arr)=3 cap(arr)=3
arr= [11 12 13]
len(arr)=3 cap(arr)=3
arr= [14 15 16]
len(arr)=6 cap(arr)=13
arr= [11 12 13 14 15 16]
len(arr)=7 cap(arr)=13
arr= [11 12 13 14 15 16 100]
len(arr)=9 cap(arr)=13
arr= [11 12 13 14 15 16 100 101 102]

Visit https: https://codeandalgo.com for more such contents

Leave a Reply

Your email address will not be published. Required fields are marked *