Каковы ключевые особенности итерирования по `map` в Go?

Ответ

Итерирование по map в Go имеет несколько важных особенностей, которые нужно знать каждому разработчику.

1. Негарантированный порядок итерации

Это самая главная особенность. При каждом новом запуске программы или даже при повторной итерации в рамках одного запуска порядок обхода элементов не гарантируется и может быть разным.

m := map[string]int{"a": 1, "b": 2, "c": 3}

// Первый запуск может вывести: b:2 a:1 c:3
// Второй запуск может вывести: c:3 b:2 a:1
for key, value := range m {
    fmt.Println(key, value)
}

Это сделано намеренно, чтобы разработчики не полагались на внутреннюю реализацию хеш-таблицы, которая может измениться в будущих версиях Go.

2. Получение отсортированного результата

Если требуется детерминированный (например, отсортированный) порядок, необходимо сначала получить все ключи, отсортировать их, а затем итерироваться по отсортированному срезу ключей.

import "sort"

keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys) // Сортируем ключи

// Итерируемся по отсортированным ключам
for _, k := range keys {
    fmt.Println(k, m[k])
}
// Гарантированный вывод: a 1, b 2, c 3

3. Модификация map во время итерации

Поведение при изменении мапы во время обхода с помощью for range четко определено:

  • Удаление элемента: Если вы удаляете элемент, который еще не был посещен итератором, он не будет посещен. Удаление уже посещенного элемента безопасно.
  • Добавление элемента: Если вы добавляете новый элемент, нет гарантии, что он будет посещен в текущем цикле итерации.

Общее правило: Безопасно удалять элементы во время итерации. Добавление новых элементов может привести к непредсказуемому поведению, поэтому его лучше избегать.

4. Получение только ключей или значений

Конструкция for range позволяет легко получать только ключи или только значения.

// Только ключи
for k := range m {
    fmt.Println("Ключ:", k)
}

// Только значения (ключ игнорируется)
for _, v := range m {
    fmt.Println("Значение:", v)
}