Получение частей коллекции
Иногда вам нужен не весь срез целиком. Нужна его часть: диапазон элементов, первые или последние несколько, куски одинакового размера или скользящее окно из соседних элементов.
В других языках у списков есть методы вроде slice, take, drop, chunked и windowed. В Go таких методов нет. Вместо них в Go есть выражение среза: s[low:high] даёт элементы с индекса low до индекса high, не включая его. Опустите low, чтобы начать с начала, или опустите high, чтобы дойти до конца.
Выражение среза ничего не копирует. Новый срез разделяет память с исходным. Если вы измените элемент в части, изменится и оригинал. Когда нужна независимая копия, используйте slices.Clone() из пакета slices.
Для задач, которые выражение среза не может решить само по себе, в этом уроке используются цикл for, пакет slices или небольшая обобщённая вспомогательная функция.
Большинство примеров на этой странице используют одни и те же данные: слова one two three four five six.
Синтаксис выражения среза
Три самые распространённые формы. n — это количество или индекс. Конечный индекс никогда не включается.
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)
Взятие среза означает выбор элементов на некоторых заданных позициях.
Для непрерывного ряда соседних позиций используйте выражение среза: words[1:4] даёт элементы с индексами 1, 2 и 3. Помните, что конечный индекс не включается: чтобы получить элементы с индексами от 1 до 3, вы пишете 1:4.
Выражение среза не может пропускать элементы. Чтобы взять каждый второй элемент или элементы на позициях, которые вы перечисляете сами, используйте цикл for и append() для добавления каждого выбранного элемента в новый срез.
Срез диапазона
words[1:4] начинается с индекса 1 и останавливается перед индексом 4. В результате получается 3 элемента.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
// индексы 1, 2 и 3 - конечный индекс 4 не включается
part := words[1:4]
fmt.Println(part)
}
Срез с шагом
Чтобы взять каждый второй элемент, цикл for с i += 2 проходит по индексам 0, 2 и 4 и собирает слова.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
// берём каждый второй элемент с индекса 0 до индекса 4
var part []string
for i := 0; i <= 4; i += 2 {
part = append(part, words[i])
}
fmt.Println(part)
}
Срез по списку индексов
Позиции 3, 5 и 0 хранятся в срезе типа int. Цикл выбирает слово на каждой позиции, поэтому результат получается в порядке индексов.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
indices := []int{3, 5, 0}
// выбираем элементы на указанных позициях, именно в этом порядке
var part []string
for _, i := range indices {
part = append(part, words[i])
}
fmt.Println(part)
}
Take и Drop
Take означает «оставить некоторое количество элементов с одного конца». Drop означает «отбросить некоторое количество элементов с одного конца и оставить остальные».
В Go и то и другое — это выражения среза. words[:3] берёт первые 3 элемента, а words[len(words)-3:] берёт последние 3. words[1:] отбрасывает первый элемент, а words[:len(words)-5] отбрасывает последние 5.
Есть также условные варианты: takeWhile, takeLastWhile, dropWhile и dropLastWhile. Они не считают элементы. Они идут с одного конца и останавливаются на первом элементе, который не проходит условие. Встроенных вариантов в Go нет, поэтому работу выполняет небольшая обобщённая вспомогательная функция: она находит индекс, где условие перестаёт выполняться, и возвращает выражение среза от этого индекса.
Взять первые или последние n элементов
words[:3] оставляет первые 3 слова. words[len(words)-3:] оставляет последние 3.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
first3 := words[:3] // первые 3 элемента
last3 := words[len(words)-3:] // последние 3 элемента
fmt.Println(first3)
fmt.Println(last3)
}
Отбросить первые или последние n элементов
words[1:] пропускает первое слово. words[:len(words)-5] пропускает последние 5 слов, поэтому остаётся только one.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
words := []string{"one", "two", "three", "four", "five", "six"}
withoutFirst := words[1:] // отбрасываем первый элемент
withoutLast5 := words[:len(words)-5] // отбрасываем последние 5 элементов
fmt.Println(withoutFirst)
fmt.Println(withoutLast5)
}
Брать, пока выполняется условие
TakeWhile оставляет слова с начала, пока не встретит слово, начинающееся с f. TakeLastWhile оставляет слова с конца, пока не встретит three. strings.HasPrefix() проверяет начало строки.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"strings"
)
// TakeWhile берёт элементы с начала, пока keep() отвечает «да», затем останавливается.
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 берёт элементы с конца, пока keep() отвечает «да», затем останавливается.
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)
}
Отбрасывать, пока выполняется условие
DropWhile пропускает слова из 3 букв в начале и оставляет всё, начиная с three. DropLastWhile пропускает слова с конца, пока они содержат букву i. strings.Contains() проверяет, содержит ли строка кусок текста.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"strings"
)
// DropWhile отбрасывает элементы с начала, пока skip() отвечает «да», и оставляет остальные.
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 отбрасывает элементы с конца, пока skip() отвечает «да», и оставляет остальные.
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 (разбиение на части)
Разбиение на части означает разрезание среза на куски одинакового размера. Последний кусок может быть короче, когда элементы не делятся поровну.
В Go это встроено начиная с версии 1.23: slices.Chunk(s, n). Она возвращает не срез срезов напрямую, а итератор, который выдаёт вам части одну за другой в цикле for range. Используйте slices.Collect(), чтобы собрать все части в [][]int.
Чтобы обработать каждую часть сразу же, пройдите циклом по частям и выполните работу внутри цикла.
Разбить на части по 3
Числа от 0 до 13 разрезаются на куски по 3. В последнем куске всего 2 элемента.
Вывод: программа на Go и захваченный вывод
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 выдаёт части одну за другой; slices.Collect собирает их в срез срезов
chunks := slices.Collect(slices.Chunk(numbers, 3))
fmt.Println(chunks)
}
Сумма каждой части
Те же самые части, но каждая складывается внутри цикла. Сохраняются только суммы.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
// складываем каждую часть из 3 элементов сразу, как только она получена
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 (скользящее окно)
Окно — это группа соседних элементов. Оконный обход двигает эту группу вдоль среза и собирает каждое её положение. В отличие от частей, окна перекрываются.
Встроенного windowed в Go нет. Это делает цикл for с выражением среза items[i:i+size]. Небольшая обобщённая вспомогательная функция Windowed делает такой код переиспользуемым. С параметром step окно сдвигается сразу на несколько позиций. Если partial равен true, короткое окно в конце сохраняется, а не отбрасывается.
Окно из 2 элементов, которое сдвигается на 1 шаг, — это то же самое, что соединить каждый элемент со следующим. В Go это цикл, который останавливается за один элемент до конца.
Окна по 3 элемента
Окно содержит 3 слова и каждый раз сдвигается на 1 позицию. Цикл останавливается, когда полное окно больше не помещается.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
// Windowed возвращает каждую группу из size соседних элементов, сдвигаясь на один шаг за раз.
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))
}
Окна с шагом и неполные окна
Окно содержит 3 числа и каждый раз сдвигается на 2 позиции. Поскольку partial равен true, последнее окно [9 10] сохраняется, хотя оно и короткое.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
// Windowed возвращает группы по size элементов, сдвигаясь каждый раз на step позиций.
// При partial = true короткая группа в конце тоже сохраняется.
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))
}
Сумма каждого окна
Каждое окно из 3 соседних элементов складывается. Сохраняются только суммы.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
func main() {
numbers := []int{1, 2, 3, 4, 5}
// складываем каждое окно из 3 соседних элементов
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 из каждого слова и его соседа. Второй цикл сравнивает тех же соседей и записывает, длиннее ли первое слово, чем второе.
Вывод: программа на Go и захваченный вывод
package main
import ("fmt")
// Pair хранит два соседних элемента.
type Pair struct {
A string
B string
}
func main() {
letters := []string{"one", "two", "three", "four", "five"}
// каждый элемент вместе со следующим за ним
var pairs []Pair
for i := 0; i+1 < len(letters); i++ {
pairs = append(pairs, Pair{letters[i], letters[i+1]})
}
fmt.Println(pairs)
// тот же обход, но сравниваем длины соседей
var longer []bool
for i := 0; i+1 < len(letters); i++ {
longer = append(longer, len(letters[i]) > len(letters[i+1]))
}
fmt.Println(longer)
}