Retrieving Collection Parts
Sometimes you do not need a whole slice. You need a part of it: a range of elements, the first or last few, pieces of equal size, or a moving window of neighbours.
Other languages have list methods like slice, take, drop, chunked and windowed. Go does not have these methods. Instead, Go has a slice expression: s[low:high] gives the elements from index low up to, but not including, index high. Leave out low to start at the beginning, or leave out high to go to the end.
A slice expression does not copy anything. The new slice shares memory with the original. If you change an element in the part, the original changes too. When you need an independent copy, use slices.Clone() from the slices package.
For the jobs that a slice expression cannot do on its own, this lesson uses a for loop, the slices package, or a small generic helper function.
Most examples on this page use the same data: the words one two three four five six.
Slice expression syntax
The three most common forms. n is a count or an index. The end index is never included.
s[low:high] // elements from index low up to, but not including, high
s[:n] // the first n elements
s[n:] // everything from index n to the end
Slice
Slicing means taking the elements at some chosen positions.
For a run of neighbouring positions, use a slice expression: words[1:4] gives the elements at indexes 1, 2 and 3. Remember that the end index is excluded: to get the elements at indexes 1 to 3 you write 1:4.
A slice expression cannot skip elements. To take every second element, or elements at positions you list yourself, use a for loop and append() each picked element to a new slice.
Slice a range
words[1:4] starts at index 1 and stops before index 4. The result has 3 elements.
Output: Go program and captured output
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
// indexes 1, 2 and 3 - the end index 4 is not included
part := words[1:4]
fmt.Println(part)
}
Slice with a step
To take every second element, a for loop with i += 2 visits indexes 0, 2 and 4 and collects the words.
Output: Go program and captured output
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
// take every second element from index 0 up to index 4
var part []string
for i := 0; i <= 4; i += 2 {
part = append(part, words[i])
}
fmt.Println(part)
}
Slice by a list of indices
The positions 3, 5 and 0 are stored in a slice of int. The loop picks the word at each position, so the result comes out in the order of the indices.
Output: Go program and captured output
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
indices := []int{3, 5, 0}
// pick the elements at the given positions, in that order
var part []string
for _, i := range indices {
part = append(part, words[i])
}
fmt.Println(part)
}
Take and Drop
Take means keep a number of elements from one end. Drop means throw away a number of elements from one end and keep the rest.
Both are slice expressions in Go. words[:3] takes the first 3 elements and words[len(words)-3:] takes the last 3. words[1:] drops the first element and words[:len(words)-5] drops the last 5.
There are also conditional versions: takeWhile, takeLastWhile, dropWhile and dropLastWhile. These do not count elements. They walk from one end and stop at the first element that fails a condition. Go has no built-in versions, so a small generic helper does the job: it finds the index where the condition stops holding and returns a slice expression from that index.
Take the first or last n elements
words[:3] keeps the first 3 words. words[len(words)-3:] keeps the last 3.
Output: Go program and captured output
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
first3 := words[:3] // the first 3 elements
last3 := words[len(words)-3:] // the last 3 elements
fmt.Println(first3)
fmt.Println(last3)
}
Drop the first or last n elements
words[1:] skips the first word. words[:len(words)-5] skips the last 5 words, so only one is left.
Output: Go program and captured output
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
withoutFirst := words[1:] // drop the first element
withoutLast5 := words[:len(words)-5] // drop the last 5 elements
fmt.Println(withoutFirst)
fmt.Println(withoutLast5)
}
Take while a condition holds
TakeWhile keeps words from the start until it meets a word that starts with f. TakeLastWhile keeps words from the end until it meets three. strings.HasPrefix() checks the start of a string.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
// TakeWhile keeps elements from the start while keep() says yes, then stops.
func TakeWhile[T any](items []T, keep func(T) bool) []T {
for i, v := range items {
if !keep(v) {
return items[:i]
}
}
return items
}
// TakeLastWhile keeps elements from the end while keep() says yes, then stops.
func TakeLastWhile[T any](items []T, keep func(T) bool) []T {
for i := len(items) - 1; i >= 0; i-- {
if !keep(items[i]) {
return items[i+1:]
}
}
return items
}
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
fromStart := TakeWhile(words, func(s string) bool { return !strings.HasPrefix(s, "f") })
fromEnd := TakeLastWhile(words, func(s string) bool { return s != "three" })
fmt.Println(fromStart)
fmt.Println(fromEnd)
}
Drop while a condition holds
DropWhile skips the 3-letter words at the start and keeps everything from three on. DropLastWhile skips words from the end while they contain the letter i. strings.Contains() checks whether a string holds a piece of text.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
// DropWhile skips elements from the start while skip() says yes, and keeps the rest.
func DropWhile[T any](items []T, skip func(T) bool) []T {
for i, v := range items {
if !skip(v) {
return items[i:]
}
}
return nil
}
// DropLastWhile skips elements from the end while skip() says yes, and keeps the rest.
func DropLastWhile[T any](items []T, skip func(T) bool) []T {
for i := len(items) - 1; i >= 0; i-- {
if !skip(items[i]) {
return items[:i+1]
}
}
return nil
}
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
fromStart := DropWhile(words, func(s string) bool { return len(s) == 3 })
fromEnd := DropLastWhile(words, func(s string) bool { return strings.Contains(s, "i") })
fmt.Println(fromStart)
fmt.Println(fromEnd)
}
Chunked
Chunking means cutting a slice into pieces of the same size. The last piece may be shorter when the elements do not divide evenly.
Go has this built in since version 1.23: slices.Chunk(s, n). It does not return a slice of slices directly. It returns an iterator, which gives you the pieces one by one in a for range loop. Use slices.Collect() to gather all the pieces into a [][]int.
To work on each piece right away, loop over the chunks and do the work inside the loop.
Split into chunks of 3
The numbers 0 to 13 are cut into pieces of 3. The last piece has only 2 elements.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
// slices.Chunk gives the pieces one by one; slices.Collect gathers them into a slice of slices
chunks := slices.Collect(slices.Chunk(numbers, 3))
fmt.Println(chunks)
}
Sum each chunk
The same chunks, but each one is added up inside the loop. Only the sums are kept.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
// add up each chunk of 3 as soon as it is produced
var sums []int
for chunk := range slices.Chunk(numbers, 3) {
sum := 0
for _, n := range chunk {
sum += n
}
sums = append(sums, sum)
}
fmt.Println(sums)
}
Windowed
A window is a group of neighbouring elements. Windowing slides the group along the slice and collects every position. Unlike chunks, windows overlap.
Go has no built-in windowed. A for loop with a slice expression items[i:i+size] does it. A small generic Windowed helper makes it reusable. With a step, the window moves several positions at a time. With partial set to true, a shorter window at the end is kept instead of being thrown away.
A window of 2 that moves 1 step is the same as pairing each element with the next one. In Go it is a loop that stops one element before the end.
Windows of 3
The window has 3 words and moves 1 position each time. The loop stops when a full window no longer fits.
Output: Go program and captured output
package main
import ("fmt")
// Windowed returns every group of size elements, moving one step at a time.
func Windowed[T any](items []T, size int) [][]T {
var result [][]T
for i := 0; i+size <= len(items); i++ {
result = append(result, items[i:i+size])
}
return result
}
func main() {
numbers := []string{"one", "two", "three", "four", "five"}
fmt.Println(Windowed(numbers, 3))
}
Windows with a step and partial windows
The window has 3 numbers and moves 2 positions each time. Because partial is true, the last window [9 10] is kept even though it is short.
Output: Go program and captured output
package main
import ("fmt")
// Windowed returns groups of size elements, moving step positions each time.
// With partial = true, a shorter group at the end is kept too.
func Windowed[T any](items []T, size, step int, partial bool) [][]T {
var result [][]T
for i := 0; i < len(items); i += step {
end := i + size
if end > len(items) {
if !partial {
break
}
end = len(items)
}
result = append(result, items[i:end])
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(Windowed(numbers, 3, 2, true))
}
Sum each window
Every window of 3 neighbours is added up. Only the sums are kept.
Output: Go program and captured output
package main
import ("fmt")
func main() {
numbers := []int{1, 2, 3, 4, 5}
// add up every window of 3 neighbours
var sums []int
for i := 0; i+3 <= len(numbers); i++ {
window := numbers[i : i+3]
sum := 0
for _, n := range window {
sum += n
}
sums = append(sums, sum)
}
fmt.Println(sums)
}
Pair each element with the next one
The first loop builds a Pair from each word and its neighbour. The second loop compares the same neighbours and records whether the first word is longer than the second.
Output: Go program and captured output
package main
import ("fmt")
// Pair holds two neighbouring elements.
type Pair struct {
A string
B string
}
func main() {
letters := []string{"one", "two", "three", "four", "five"}
// each element together with the one after it
var pairs []Pair
for i := 0; i+1 < len(letters); i++ {
pairs = append(pairs, Pair{letters[i], letters[i+1]})
}
fmt.Println(pairs)
// the same walk, but compare the lengths of the neighbours
var longer []bool
for i := 0; i+1 < len(letters); i++ {
longer = append(longer, len(letters[i]) > len(letters[i+1]))
}
fmt.Println(longer)
}