Практические задачи со срезами

1. Генерация случайных чисел

Задача

Создать срез из 5 случайных чисел в диапазоне 0-99.

Решение с предварительным выделением памяти

randomNums := make([]int, 0, 5)  // len=0, cap=5

for i := 0; i < 5; i++ {
    randomNums = append(randomNums, rand.Intn(100))
}

fmt.Println(randomNums)  // [42 17 89 3 56]

Почему make([]int, 0, 5)?
Заранее резервируем ёмкость для 5 элементов, чтобы избежать лишних перевыделений памяти.


Альтернатива: без append

randomNums := make([]int, 5)  // len=5, cap=5

for i := 0; i < 5; i++ {
    randomNums[i] = rand.Intn(100)
}

fmt.Println(randomNums)  // [42 17 89 3 56]

Разница:

  • Первый способ: len=0, добавляем через append
  • Второй способ: len=5, присваиваем по индексу

2. Фильтрация чётных чисел

Задача

Из исходного среза выбрать только чётные числа.

Решение через for range

allNums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evenNums := []int{}  // Пустой срез

for _, num := range allNums {
    if num%2 == 0 {
        evenNums = append(evenNums, num)
    }
}

fmt.Println(allNums)   // [1 2 3 4 5 6 7 8 9 10]
fmt.Println(evenNums)  // [2 4 6 8 10]

Механизм:

  1. Перебираем исходный срез
  2. Проверяем условие num % 2 == 0
  3. Если истина — добавляем в новый срез

Классический for (альтернатива)

evenNums := []int{}

for i := 0; i < len(allNums); i++ {
    if allNums[i]%2 == 0 {
        evenNums = append(evenNums, allNums[i])
    }
}

3. Разворот (реверс) среза

Задача

Развернуть срез в обратном порядке in-place (без создания нового).

Решение: обмен элементов с концов

toReverse := []int{1, 2, 3, 4, 5}
fmt.Println(toReverse)  // [1 2 3 4 5]

for i, j := 0, len(toReverse)-1; i < j; i, j = i+1, j-1 {
    toReverse[i], toReverse[j] = toReverse[j], toReverse[i]
}

fmt.Println(toReverse)  // [5 4 3 2 1]

Разбор цикла

for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1
Компонент Описание
i, j := 0, len(s)-1 Два счётчика: начало и конец
i < j Пока не встретились в середине
i, j = i+1, j-1 i движется вправо, j влево

Визуализация:

Итерация 1: i=0, j=4 → меняем [1, 2, 3, 4, 5] → [5, 2, 3, 4, 1]
Итерация 2: i=1, j=3 → меняем [5, 2, 3, 4, 1] → [5, 4, 3, 2, 1]
Итерация 3: i=2, j=2 → i >= j, стоп

Специфичная форма цикла for

Из транскрипта:
“Специфичная форма цикла for, которую вы скорее всего в реальных задачах не увидите, но тем не менее делать это можно”.

for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
    // Два счётчика изменяются одновременно
}

Особенности:

  • Инициализация двух переменных через запятую
  • Обновление обеих переменных в одной строке
  • Нетипично для других языков программирования

4. Обмен значений (swap)

Идиоматичный Go-способ

a, b := 1, 2
a, b = b, a  // Без временной переменной!

fmt.Println(a, b)  // 2 1

В контексте среза:

s[i], s[j] = s[j], s[i]

5. Сводная таблица паттернов

Задача Паттерн Ключевая идея
Генерация N элементов make([]T, 0, N) + append Резервируем cap заранее
Фильтрация for range + условие + append Новый срез с подходящими элементами
Реверс Два указателя i, j + swap Меняем элементы с концов к центру

6. Почему резервировать cap заранее?

Без резервирования (много перевыделений)

s := []int{}  // cap=0

for i := 0; i < 1000; i++ {
    s = append(s, i)  // Множество перевыделений памяти
}

Проблема: При len == cap каждый append создаёт новый массив.


С резервированием (одно выделение)

s := make([]int, 0, 1000)  // cap=1000

for i := 0; i < 1000; i++ {
    s = append(s, i)  // Без перевыделений
}

Оптимизация: Память выделяется один раз для 1000 элементов.


7. Фильтрация с сохранением порядка

Задача: удалить отрицательные числа

nums := []int{-5, 3, -2, 7, -1, 9}
positive := []int{}

for _, num := range nums {
    if num >= 0 {
        positive = append(positive, num)
    }
}

fmt.Println(positive)  // [3 7 9]

8. Реверс без изменения оригинала

original := []int{1, 2, 3, 4, 5}

// Создаём копию
reversed := make([]int, len(original))
for i := 0; i < len(original); i++ {
    reversed[i] = original[len(original)-1-i]
}

fmt.Println(original)  // [1 2 3 4 5]
fmt.Println(reversed)  // [5 4 3 2 1]

9. Итоги по паттернам

Генерация данных

✅ Используйте make([]T, 0, capacity) для известного размера ✅ Заполняйте через append() или прямое присваивание

Фильтрация

✅ Создавайте новый срез: filtered := []T{}
✅ Перебирайте через for range ✅ Добавляйте элементы, удовлетворяющие условию

Реверс

✅ Два указателя: i, j := 0, len(s)-1 ✅ Swap: s[i], s[j] = s[j], s[i]
✅ Цикл: for i < j


10. Ключевые принципы

Предварительное выделение памяти через make([]T, 0, cap) экономит операции
Фильтрация всегда создаёт новый срез через append()
Реверс через обмен элементов с концов — классический алгоритм
Множественное присваивание i, j = i+1, j-1 — идиома Go ✅ Специфичные формы циклов возможны, но редко используются на практике

Формула успеха:

Знание задачи → Выбор паттерна → Применение функций (append, make, copy)