Получение отдельных элементов
В этом уроке показано, как получить один элемент из слайса: по его позиции, по условию или случайно. Также показано, как проверить, существует ли элемент вообще.
В Go нет null. Когда поиск может не дать результата, функция в Go возвращает два значения: элемент и bool с именем ok. Это тот же шаблон value, ok, который вы знаете по map.
Чтение позиции, которой не существует, например words[5] в слайсе из 5 элементов, останавливает программу паникой (ошибкой во время выполнения). Поэтому мы всегда сначала проверяем len().
В других языках для этого есть много готовых функций. Go остаётся небольшим языком: вы используете квадратные скобки, пакет slices или маленькую вспомогательную функцию, которую пишете один раз.
Получение по позиции
У каждого элемента слайса есть позиция, которая называется индекс. Первый индекс — 0, поэтому последний — len(words)-1.
Чтобы прочитать элемент по позиции, напишите индекс в квадратных скобках: words[3]. Именно так в Go читают элемент на нужной позиции, первый элемент или последний.
В Go нет elementAtOrNull() или elementAtOrElse(). Маленькая обобщённая вспомогательная функция сравнивает индекс с len() и возвращает либо value, ok, либо результат запасной функции. Так индекс вне диапазона не может вызвать панику.
Элемент на позиции
words[3] читает элемент с индексом 3. Так как счёт начинается с 0, это четвёртое слово.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Позиции начинаются с 0, поэтому индекс 3 — это четвёртый элемент.
fmt.Println(words[3])
}
Первый и последний элемент
Первый элемент — это words[0]. Последний — words[len(words)-1], потому что длина равна 5, а последний индекс — 4.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Первый элемент имеет индекс 0, последний — len - 1.
fmt.Println(words[0])
fmt.Println(words[len(words)-1])
}
Элемент на позиции или ok == false
ElementAt сравнивает индекс с len(). Индекс 1 находится в диапазоне, поэтому функция возвращает "two" и true. Индекс 5 вне диапазона, поэтому вместо паники она возвращает нулевое значение "" и false.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
// ElementAt возвращает элемент с индексом i и true либо нулевое значение и false.
func ElementAt[T any](items []T, i int) (T, bool) {
if i < 0 || i >= len(items) {
var zero T
return zero, false
}
return items[i], true
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
v1, ok1 := ElementAt(words, 1)
fmt.Printf("%q %v\n", v1, ok1)
// Индекса 5 не существует: паники нет, просто ok == false.
v2, ok2 := ElementAt(words, 5)
fmt.Printf("%q %v\n", v2, ok2)
}
Элемент на позиции или запасное значение
ElementAtOrElse принимает запасную функцию. Эта функция получает индекс, поэтому может построить сообщение вроде «Значение по индексу 5 не определено».
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
// ElementAtOrElse возвращает элемент с индексом i или результат fallback(i).
func ElementAtOrElse[T any](items []T, i int, fallback func(int) T) T {
if i < 0 || i >= len(items) {
return fallback(i)
}
return items[i]
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(ElementAtOrElse(words, 5, func(i int) string {
return fmt.Sprintf("Значение по индексу %d не определено", i)
}))
}
Получение по условию
Часто позиция вам неизвестна. Вместо неё известно условие: первое слово длиннее 3 букв, последнее слово, которое начинается с «f». Условие — это функция, которая принимает один элемент и возвращает true или false.
Для первого совпадения в пакете slices есть slices.IndexFunc(). Она возвращает индекс первого элемента, который проходит проверку, или -1, если такого элемента нет. Затем вы читаете элемент по этому индексу.
Функции для последнего совпадения в slices нет. Цикл for, который идёт вниз от len(words)-1, находит последнее совпадение: первое попадание при движении назад и есть последнее в слайсе.
Других готовых функций поиска в Go нет. Маленькие обобщённые вспомогательные функции Find и FindLast справляются с этой задачей и возвращают value, ok, поэтому «ничего не совпало» — это просто ok == false.
Первое совпадение
slices.IndexFunc() даёт индекс первого слова, для которого len(s) > 3. Это слово — "three".
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
// IndexFunc даёт индекс первого элемента, который проходит проверку.
i := slices.IndexFunc(words, func(s string) bool { return len(s) > 3 })
fmt.Println(words[i])
}
Последнее совпадение
Цикл начинается с последнего индекса и идёт вниз. Первое встреченное слово, которое начинается с "f", — это "five", поэтому цикл печатает его и останавливается через break.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"strings"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Идём с конца, поэтому первое попадание — это последнее совпадение.
for i := len(words) - 1; i >= 0; i-- {
if strings.HasPrefix(words[i], "f") {
fmt.Println(words[i])
break
}
}
}
Первое совпадение или ok == false
Ни одно слово не длиннее 6 букв, поэтому Find возвращает ok == false и программа печатает «не найдено». В слайсе чисел первое чётное число — 2.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
// Find возвращает первый элемент, который проходит проверку, и true либо нулевое значение и false.
func Find[T any](items []T, match func(T) bool) (T, bool) {
for _, v := range items {
if match(v) {
return v, true
}
}
var zero T
return zero, false
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
numbers := []int{1, 2, 3, 4}
if w, ok := Find(words, func(s string) bool { return len(s) > 6 }); ok {
fmt.Println(w)
} else {
fmt.Println("не найдено")
}
if n, ok := Find(numbers, func(n int) bool { return n%2 == 0 }); ok {
fmt.Println(n)
}
}
Последнее совпадение или ok == false
FindLast идёт по числам с конца. Последнее чётное число — 4, и оно находится сразу же.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
// FindLast идёт с конца и возвращает последний элемент, который проходит проверку.
func FindLast[T any](items []T, match func(T) bool) (T, bool) {
for i := len(items) - 1; i >= 0; i-- {
if match(items[i]) {
return items[i], true
}
}
var zero T
return zero, false
}
func main() {
numbers := []int{1, 2, 3, 4}
if n, ok := FindLast(numbers, func(n int) bool { return n%2 == 0 }); ok {
fmt.Println(n)
}
}
Получение через селектор
Иногда нужно сначала изменить каждый элемент, а затем взять первый подходящий результат.
В Go такой функции нет. Это делает цикл for: преобразуйте каждый элемент, проверьте результат и сделайте break на первом подходящем. Слайс здесь имеет тип []any, поэтому может хранить одновременно число, строку и bool. fmt.Sprint() превращает любое значение в текст.
Первое подходящее преобразованное значение
Каждое значение превращается в текст с помощью fmt.Sprint(). 0 становится "0", и это слишком коротко. В "true" 4 символа, поэтому оно печатается и цикл останавливается.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
func main() {
list := []any{0, "true", false}
// Превращаем каждое значение в текст и останавливаемся на первом тексте из 4 и более символов.
for _, v := range list {
s := fmt.Sprint(v)
if len(s) >= 4 {
fmt.Println(s)
break
}
}
}
Случайный элемент
Чтобы выбрать случайный элемент, выберите случайный индекс. Пакет math/rand даёт случайные числа; r.Intn(n) возвращает число от 0 до n-1, а это как раз диапазон допустимых индексов.
В Go у слайсов нет метода random(), поэтому индекс, как обычно, пишется в квадратных скобках.
Источник случайных чисел, созданный через rand.NewSource(42), начинает работу с фиксированного зерна (seed). С одним и тем же зерном программа каждый раз выбирает один и тот же элемент, и вывод становится предсказуемым. Не задавайте зерно, если хотите получать разный результат при каждом запуске.
Выбор случайного элемента
С зерном 42 источник выбирает индекс 1, поэтому программа печатает 2. Измените зерно — и может получиться другое число.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"math/rand"
)
func main() {
numbers := []int{1, 2, 3, 4}
// Фиксированное зерно делает «случайный» выбор одинаковым при каждом запуске.
r := rand.New(rand.NewSource(42))
i := r.Intn(len(numbers))
fmt.Println(numbers[i])
}
Проверка наличия элемента
Чтобы проверить, есть ли значение в слайсе, используйте slices.Contains(). Она возвращает true или false.
В Go нет containsAll(). Маленькая обобщённая вспомогательная функция проходит по нужным значениям и вызывает slices.Contains() для каждого из них. Она возвращает false, как только одного значения не хватает.
В Go также нет isEmpty() или isNotEmpty(). Сравнивайте длину: len(s) == 0 означает «пусто», а len(s) > 0 означает «не пусто».
Содержит одно значение
"four" есть в слайсе, поэтому первая проверка даёт true. "zero" в слайсе нет, поэтому вторая проверка даёт false.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(slices.Contains(words, "four"))
fmt.Println(slices.Contains(words, "zero"))
}
Содержит все значения
И "four", и "one" есть в слайсе, поэтому ContainsAll возвращает true. "zero" отсутствует, поэтому второй вызов возвращает false.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"slices"
)
// ContainsAll истинна только тогда, когда каждое нужное значение есть в items.
func ContainsAll[T comparable](items []T, wanted []T) bool {
for _, w := range wanted {
if !slices.Contains(items, w) {
return false
}
}
return true
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(ContainsAll(words, []string{"four", "one"}))
fmt.Println(ContainsAll(words, []string{"one", "zero"}))
}
Пустой или не пустой
У empty длина 0, поэтому len(empty) == 0 — это true. В words 5 элементов, поэтому len(words) > 0 — тоже true.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
empty := []string{}
// Слайс пуст, когда его длина равна 0.
fmt.Println(len(empty) == 0)
fmt.Println(len(words) > 0)
}