Filtering a Collection
Filtering means picking out the elements of a collection that match a condition.
The condition is written as a function that takes one element and returns true (keep it) or false (skip it). Such a function is called a predicate.
Filtering builds a new slice. The original slice is not changed.
Go has no built-in filter method on slices. You filter with a for loop and an if, or you write a small generic helper once and reuse it. The helper below works for a slice of any type.
The standard library has slices.DeleteFunc(), which removes elements that match a condition. Be careful: it changes the original slice instead of making a new one.
Generic Filter helper syntax
T stands for the element type. keep is the predicate: elements for which it returns true are copied into result.
func Filter[T any](items []T, keep func(T) bool) []T {
var result []T
for _, v := range items {
if keep(v) {
result = append(result, v)
}
}
return result
}
Filter by Predicate
The simplest way to filter is to pass a predicate to the Filter helper. The helper walks through the slice and keeps every element that passes the test.
Maps have no helper of their own. To filter a map, loop over it with range and copy the matching key:value pairs into a new map.
If the condition also needs the position of the element, use the index that range gives you together with the value.
To keep the elements that do not match, put ! in front of the condition. Go has no separate filterNot.
A slice of type []any can hold values of different types. A type assertion such as v.(string) checks whether a value is a string and gives it back with that type. A pointer can be nil, which means "no value"; compare with nil to skip such entries.
Filter a slice
Keep only the words that are longer than 3 letters. The condition len(s) > 3 is passed to Filter as a function.
Output: Go program and captured output
package main
import "fmt"
// Filter returns a new slice with only the elements that keep() says yes to.
func Filter[T any](items []T, keep func(T) bool) []T {
var result []T
for _, v := range items {
if keep(v) {
result = append(result, v)
}
}
return result
}
func main() {
words := []string{"one", "two", "three", "four"}
longerThan3 := Filter(words, func(s string) bool { return len(s) > 3 })
fmt.Println(longerThan3)
}
Filter a map
Loop over the map and keep the pairs whose key ends with "1" and whose value is greater than 10. strings.HasSuffix() checks the end of a string.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
func main() {
numbersMap := map[string]int{"key1": 1, "key2": 2, "key3": 3, "key11": 11}
// A map has no built-in filter, so loop and copy the matching pairs into a new map.
filteredMap := map[string]int{}
for key, value := range numbersMap {
if strings.HasSuffix(key, "1") && value > 10 {
filteredMap[key] = value
}
}
fmt.Println(filteredMap)
}
Filter using the index
range gives the index and the value. Keep a word only when its index is not 0 and it has fewer than 5 letters.
Output: Go program and captured output
package main
import "fmt"
func main() {
numbers := []string{"one", "two", "three", "four"}
// range gives both the index and the value, so the condition can use both.
var filteredIdx []string
for index, s := range numbers {
if index != 0 && len(s) < 5 {
filteredIdx = append(filteredIdx, s)
}
}
fmt.Println(filteredIdx)
}
Filter with a negated condition
Drop the words with 3 letters or fewer. The condition len(s) = 3 is negated with !, so only the longer words stay.
Output: Go program and captured output
package main
import "fmt"
func Filter[T any](items []T, keep func(T) bool) []T {
var result []T
for _, v := range items {
if keep(v) {
result = append(result, v)
}
}
return result
}
func main() {
numbers := []string{"one", "two", "three", "four"}
// Go has no filterNot: negate the condition with ! and use Filter.
filteredNot := Filter(numbers, func(s string) bool { return !(len(s) <= 3) })
fmt.Println(filteredNot)
}
Filter by type
The slice holds nil, a number, two strings and a float. The type assertion v.(string) with the ok value finds the strings. Each one is printed in upper case.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
func main() {
numbers := []any{nil, 1, "two", 3.0, "four"}
// v.(string) asks: is this value a string? ok is true only when it is.
for _, v := range numbers {
if s, ok := v.(string); ok {
fmt.Println(strings.ToUpper(s))
}
}
}
Filter out nil values
The slice holds string pointers, and some of them are nil. Keep the pointers that are not nil and collect the values they point to with *p.
Output: Go program and captured output
package main
import "fmt"
func main() {
one, two := "one", "two"
// A pointer can be nil, which means "no value".
nullableList := []*string{nil, &one, nil, &two}
var values []string
for _, p := range nullableList {
if p != nil {
values = append(values, *p)
}
}
fmt.Println(values)
}
Partition
Sometimes you want both groups: the elements that match and the ones that do not.
Go has no built-in partition. A small generic helper does it: it loops once and appends each element to either the match slice or the rest slice. The helper returns both slices, because a Go function can return more than one value.
Split a slice in two
Words longer than 3 letters go into match; all the others go into rest. Both slices are printed.
Output: Go program and captured output
package main
import "fmt"
// Partition splits items into two slices: the ones that match and the rest.
func Partition[T any](items []T, test func(T) bool) ([]T, []T) {
var match, rest []T
for _, v := range items {
if test(v) {
match = append(match, v)
} else {
rest = append(rest, v)
}
}
return match, rest
}
func main() {
numbers := []string{"one", "two", "three", "four"}
match, rest := Partition(numbers, func(s string) bool { return len(s) > 3 })
fmt.Println(match)
fmt.Println(rest)
}
Test Predicates
Sometimes you do not need the matching elements, only a yes or no answer.
- any: does at least one element pass the test? Go's
slices.ContainsFunc()answers this. - none: does no element pass the test? Put
!in front ofslices.ContainsFunc(). - all: does every element pass the test? Go has no function for this, so use a small generic
Allhelper.
Without a condition, "any element at all?" is len(s) > 0 and "no element at all?" is len(s) == 0.
On an empty slice, All returns true. There is no element that fails the test, so the test is never broken. This is called vacuous truth.
any
Check whether at least one word ends with "e", then check whether the slice has any element at all.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
numbers := []string{"one", "two", "three", "four"}
// slices.ContainsFunc is true if at least one element passes the test.
fmt.Println(slices.ContainsFunc(numbers, func(s string) bool { return strings.HasSuffix(s, "e") }))
// "any element at all?" is just a length check.
fmt.Println(len(numbers) > 0)
}
none
Check that no word ends with "a", then check whether the slice is empty.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
numbers := []string{"one", "two", "three", "four"}
// "none" is the opposite of "any": no element passes the test.
fmt.Println(!slices.ContainsFunc(numbers, func(s string) bool { return strings.HasSuffix(s, "a") }))
// "no element at all?" is just a length check.
fmt.Println(len(numbers) == 0)
}
all
Check whether every word ends with "e". Then call All on an empty slice, which gives true.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
// All is true only when every element passes the test.
func All[T any](items []T, test func(T) bool) bool {
for _, v := range items {
if !test(v) {
return false
}
}
return true
}
func main() {
numbers := []string{"one", "two", "three", "four"}
empty := []string{}
fmt.Println(All(numbers, func(s string) bool { return strings.HasSuffix(s, "e") }))
// An empty slice has no element that fails, so All is true.
fmt.Println(All(empty, func(s string) bool { return strings.HasSuffix(s, "e") }))
}