Практика 6. `append`, `copy`, `len`, `cap` и общее хранилище slices
Цель
Понять внутреннюю модель slice: длину, вместимость, общее базовое хранилище и
возможное перевыделение при append. Научиться создавать независимые копии,
вставлять и удалять элементы, сортировать данные без изменения оригинала.
Общие требования
- создайте модуль
example.com/practice-06; - каждый эксперимент сопровождайте фактическим выводом
lenиcap; - не полагайтесь на конкретный коэффициент роста вместимости;
- операции, обещающие сохранить исходные данные, должны действительно создавать независимый результат;
- выполните
go fmt ./...,go test ./...,go vet ./....
A. Эксперименты
Задание 1. Рост slice
Создайте:
values := make([]int, 0, 2)Добавьте числа от 1 до 12. После каждого append выведите:
value=1 len=1 cap=2 values=[1]Ответьте в README.md:
- В какие моменты менялась вместимость?
- Совпадает ли она всегда с длиной?
- Можно ли рассчитывать на конкретную последовательность значений
cap?
Задание 2. Длина не равна вместимости
Создайте:
values := make([]int, 3, 8)Заполните существующие три элемента через индексы, затем добавьте ещё два через
append. После каждого шага выведите данные, длину и вместимость.
Объясните, почему сразу после make доступны индексы 0–2, но индекс 3 ещё
недоступен, несмотря на cap == 8.
Задание 3. Общее хранилище
Запустите и объясните:
numbers := []int{10, 20, 30, 40, 50}left := numbers[:3]right := numbers[2:]
left[2] = 999
fmt.Println("numbers:", numbers)fmt.Println("left:", left)fmt.Println("right:", right)Нарисуйте в README.md простую схему: один базовый массив и два диапазона,
которые на него указывают.
Задание 4. append к подсрезу
Исследуйте:
numbers := []int{10, 20, 30, 40}part := numbers[:2]part = append(part, 777)Выведите numbers и part. Затем повторите с ограничением вместимости:
numbers := []int{10, 20, 30, 40}part := numbers[:2:2]part = append(part, 777)Объясните различие.
B. Основные задания
Задание 5. Независимая копия
Реализуйте два варианта:
func cloneWithCopy(values []int) []intfunc cloneWithSlices(values []int) []intПервый использует make и copy, второй — slices.Clone.
Проверка должна:
- клонировать исходные данные;
- изменить первый элемент клона;
- доказать, что оригинал не изменился;
- корректно обработать
nilи пустой slice.
Задание 6. Удаление
Реализуйте:
func deleteAt(values []int, index int) ([]int, bool)Требования:
- некорректный индекс возвращает исходный slice и
false; - корректный индекс удаляется с сохранением порядка;
- функция возвращает новый slice;
- исходный slice и его базовое хранилище не должны изменяться.
Подсказка: сначала клонируйте данные, затем используйте slices.Delete.
Дополнительно реализуйте быстрый вариант без сохранения порядка и объясните, в каких задачах он допустим.
Задание 7. Вставка
func insertAt(values []int, index int, value int) ([]int, bool)Разрешите индексы от 0 до len(values) включительно. Индекс len(values)
означает добавление в конец. Исходный slice не изменяется.
Используйте slices.Insert после проверки границы и клонирования.
Задание 8. Сортировка без изменения оригинала
func sortedCopy(values []int) []intФункция должна:
- вернуть независимую копию;
- отсортировать её по возрастанию;
- оставить исходный порядок без изменений.
Дополнительно создайте вариант по убыванию через slices.SortFunc.
Задание 9. Уникальные соседние элементы
Дан отсортированный slice:
values := []int{1, 1, 2, 2, 2, 4, 5, 5}Получите [1 2 4 5] через slices.Compact. Не изменяйте оригинал: сначала
создайте копию.
Объясните, почему slices.Compact удаляет только соседние повторы и зачем для
произвольного набора может понадобиться сортировка или map.
Задание 10. Фильтрация с повторным использованием памяти
Реализуйте:
func positiveCopy(values []int) []intfunc positiveInPlace(values []int) []intПервый вариант создаёт независимый результат. Второй использует:
result := values[:0]Покажите, как второй вариант изменяет базовое хранилище исходного slice. В
README.md опишите, когда такой вариант допустим и когда опасен.
C. Проект «Редактор списка задач»
Продолжите проект из практики 5. Пока данные хранятся в двух согласованных slices:
titles := []string{ "Подготовить отчёт", "Проверить почту", "Исправить ошибку",}
priorities := []int{5, 2, 4}Индекс названия должен соответствовать индексу приоритета. Реализуйте:
func validateStorage(titles []string, priorities []int) bool
func addTask( titles []string, priorities []int, title string, priority int,) (newTitles []string, newPriorities []int, ok bool)
func deleteTask( titles []string, priorities []int, index int,) (newTitles []string, newPriorities []int, ok bool)
func insertTask( titles []string, priorities []int, index int, title string, priority int,) (newTitles []string, newPriorities []int, ok bool)Требования:
- функции проверяют одинаковую длину двух slices;
- приоритет находится в диапазоне 1–5;
- название после
strings.TrimSpaceне пустое; - индекс проверяется до обращения к элементу;
- операции возвращают независимые slices;
- при ошибке исходные данные не меняются;
- вызывающая сторона сохраняет оба возвращённых slice;
- после каждой операции вызывается
validateStorage.
Сценарий проверки:
- добавьте задачу в конец;
- вставьте задачу в начало;
- вставьте задачу в середину;
- удалите последнюю задачу;
- попробуйте удалить по отрицательному индексу;
- попробуйте добавить приоритет 8;
- докажите, что исходный snapshot не изменился.
Для snapshot используйте:
originalTitles := slices.Clone(titles)originalPriorities := slices.Clone(priorities)Аналитический вопрос
Почему два согласованных slice неудобны и опасны? Как структура Task, которая
будет изучена в следующей лекции, устранит эту проблему?
Дополнительные задания
Ограничение удерживаемой памяти
Создайте slice на 10 миллионов элементов и оставьте первые 10. Сравните два варианта:
small := large[:10]и:
small := slices.Clone(large[:10])Объясните разницу без попытки вручную управлять сборщиком мусора.
Очередь
Реализуйте простую FIFO-очередь на slice с функциями enqueue и dequeue.
dequeue возвращает значение, новый slice и bool. Обработайте пустую очередь.
Критерии оценивания
| Критерий | Баллы |
|---|---|
Эксперименты с len, cap и append | 0–3 |
| Понимание общего хранилища | 0–3 |
| Копирование, вставка и удаление | 0–3 |
| Сортировка и фильтрация | 0–3 |
| Проект «Редактор списка задач» | 0–4 |
| Максимум | 16 |
Вопросы для защиты
- Что хранит значение slice?
- Почему доступ ограничен
len, а неcap? - Когда
appendможет изменить исходный массив? - Что означает запись
values[:2:2]? - Как создать независимую копию slice?
- Почему
slices.Sortможет изменить данные вызывающей стороны? - Чем фильтрация на месте отличается от создания нового результата?
- Почему две связанные последовательности лучше заменить slice структур?