08 Апреля 2024

Карты и Go

Карты в Go — это встроенный тип данных, предоставляющий возможность хранения пар ключ-значение. Они похожи на словари или ассоциативные массивы в других языках программирования.

Особенности карт в Go:

  • Динамический размер: Карты могут расти и уменьшаться по мере добавления и удаления элементов, что делает их гибкими для использования в ситуациях, когда количество элементов заранее неизвестно.

  • Типизированные ключи и значения: В картах Go и ключи, и значения могут быть почти любого типа, за исключением тех, которые содержат срезы, карты или другие несравнимые типы. Тип ключа и тип значения для карты указываются при её объявлении.

  • Быстрый доступ: Карты предоставляют быстрый доступ к значениям по ключам. Время доступа к элементу карты не зависит от размера карты, что делает их эффективным выбором для поиска данных.

Работа с картами:

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"fmt"

)

func main() {

// Объявление карты

var myMap map[int]string

fmt.Println(myMap) // map[]

// Инициализация карты

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

// Или можно объявить и инициализировать карту одновременно

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

/*

Или можно объявить и инициализировать карту одновременно с емкостью


В отличие от слайсов, у карт нет понятия внешней емкости, доступной для проверки с помощью функции cap().

Это означает, что вызов cap(myMap) будет ошибочным, так как функция cap() не применима к картам.


Начальная емкость карты в Go служит лишь подсказкой для реализации о том, сколько элементов ожидается в карте.

Это может помочь оптимизировать ее внутреннюю структуру и уменьшить количество аллокаций памяти в

начальной фазе использования карты.

*/

myMap = make(map[int]string, 5)

fmt.Println(myMap, len(myMap)) // map[] 0


// Или можно объявить и инициализировать карту одновременно значениями

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

fmt.Println(myMap) // map[1:a 2:b 3:c]

key := 5

// Добавить или изменить значение для ключа "apple"

myMap[key] = "apple"

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Получить значение. Если ключ существует, `ok` будет true, иначе false.

if _, ok := myMap[key]; !ok {

panic(fmt.Sprintf("myMap: key %d not found", key)) // panic: myMap: key 6 not found, если ключа 6 не окажется в карте

}

// Удалить элемент с ключом "apple"

// Ну и ни чего не происходит если такого ключа нет

delete(myMap, 6)

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Итерация по карте

for k, v := range myMap {

fmt.Println(k, v)

}

}

Подводные камни

1. Изменение карты во время итерации

Модификация карты во время итерации по ней может привести к непредсказуемому поведению. Например, вы можете пропустить некоторые элементы или столкнуться с паникой в рантайме.

Это допустимо в Go и не приведет к непосредственной ошибке или панике.

В случае с Go, поведение при удалении элементов из карты во время итерации по ней довольно определенное и безопасное. Итератор карты в Go спроектирован так, чтобы обрабатывать изменения карты во время итерации, но это может привести к тому, что некоторые элементы будут пропущены в процессе итерации, если вы удаляете элементы впереди текущего элемента итерации.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

func main() {

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

for k := range m {

if k == 2 {

delete(m, 3) // Попытка удалить ключ во время итерации

}

}

// Поведение не определено. Возможно пропуск элементов или другие непредвиденные результаты.

}

Решение.

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

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import "fmt"

func main() {

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

var keysToDelete []int

for k := range m {

if k == 2 {


// добавляем ключи для удаления

keysToDelete = append(keysToDelete, 3)

}

}

// Удаляем элементы после итерации

for _, k := range keysToDelete {

delete(m, k)

}

fmt.Println(m) // Вывод: map[1:a 2:b], элемент с ключом 3 удален

}

2. Неинициализированная карта

Попытка использования неинициализированной карты вызовет панику во время выполнения.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

func main() {

// Попытка использования неинициализированной карты вызовет панику во время выполнения.

var m map[int]string

m[1] = "apple" // panic: assignment to entry in nil map

// Чтобы избежать этого, карта должна быть инициализирована перед использованием

m = make(map[int]string)


m[1] = "apple" // OK

}

3. Проверка наличия ключа

При получении значения из карты, если ключ не существует, возвращаемое значение будет нулевым для типа значения карты. Это может ввести в заблуждение, если не проверять, действительно ли ключ существует.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"fmt"

)

func main() {

m := make(map[int]int)

k := 123

val := m[1] // val == 0, ключ 123 не существует, но 0 также может быть допустимым значением

fmt.Println(val)

// Лучше использовать двухзначную форму получения элемента:

if _, ok := m[k]; !ok {

fmt.Printf("map key: %d, not found\n", k)

}

}

4. Сортировка

В Go карты не имеют внутреннего порядка, и их элементы хранятся в случайном порядке. Однако вы можете отсортировать ключи (или значения) карты, используя дополнительные шаги. Вот как можно отсортировать карту по ключам:

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"fmt"

"sort"

)

func main() {

m := map[string]int{

"banana": 3,

"apple":  5,

"pear":  2,

"orange": 4,

}

// Создаем слайс для хранения ключей

var keys []string

// Добавляем ключи в слайс

for k := range m {

keys = append(keys, k)

}

// Сортируем ключи

sort.Strings(keys)


// Выводим отсортированную карту

for _, k := range keys {

fmt.Println(k, m[k])

}

}

5. Конкурентная модификация

Карты в Go не являются потокобезопасными, и одновременная модификация карты из разных горутин может привести к состоянию гонки и панике.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import "time"

func main() {

m := make(map[int]int)

// Имитация конкурентной модификации

go func() {

for i := 0; i < 10000; i++ {

m[i] = i

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

// Возможна паника или другое непредсказуемое поведение

time.Sleep(time.Second * 100)

}

Решение.

Для предотвращения конкурентной модификации карты и обеспечения потокобезопасности, можно использовать мьютекс из пакета sync. Мьютекс позволяет блокировать доступ к ресурсу (в данном случае к карте) для всех горутин, кроме одной, которая в данный момент владеет мьютексом. Это гарантирует, что только одна горутина может изменять карту в любой момент времени.

Пример решения с использованием мьютекса:

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"sync"

"time"

)

func main() {

var m = make(map[int]int) // Создаем карту

var mutex sync.Mutex // Создаем мьютекс

// Горутина для добавления элементов в карту

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

m[i] = i

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Горутина для удаления элементов из карты

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

delete(m, i)

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Даем горутинам время на выполнение

time.Sleep(time.Second)

}

В данном примере перед каждым изменением карты мьютекс блокируется вызовом mutex.Lock(), и разблокируется вызовом mutex.Unlock() после изменения. Это гарантирует, что в любой момент времени карта может быть изменена только одной горутиной, предотвращая конкурентную модификацию и потенциальные ошибки в программе.

Использование каналов: Каналы в Go могут использоваться не только для обмена данными между горутинами, но и как средство синхронизации. Вы можете создать канал, через который будут передаваться операции чтения и записи карты, а одна горутина будет отвечать за выполнение этих операций. Это гарантирует, что все операции с картой выполняются последовательно.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"sync"

)

type command struct {

key  int

value int

op  string // "set", "delete", "get"

}

func main() {

var opChan = make(chan command)

var wg sync.WaitGroup

// Горутина для управления картой

go func() {

m := make(map[int]int)

for op := range opChan {

switch op.op {

case "set":

m[op.key] = op.value

case "delete":

delete(m, op.key)

}

}

}()

// Горутина для добавления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, value: i, op: "set"}

}

}()

// Горутина для удаления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, op: "delete"}

}

}()

wg.Wait()

close(opChan)

}

sync.Map: В пакете sync есть специальная структура Map, предназначенная для использования в многопоточных средах без явного использования мьютексов для синхронизации. sync.Map имеет встроенные методы для безопасной работы с данными в конкурентных ситуациях.

Карты и Go Golang, Программирование, Карты, Длиннопост

package main

import (

"fmt"

"sync"

)

func main() {

var m sync.Map

// Установка значения

m.Store(1, "a")

// Получение значения

if val, ok := m.Load(1); ok {

fmt.Println("Value:", val)

}

// Удаление значения

m.Delete(1)

}

Оба подхода имеют свои преимущества и недостатки. Использование каналов может быть более наглядным и понятным, но потребует больше кода для управления операциями. sync.Map более прост в использовании для простых случаев, но может быть менее гибким, чем полное управление синхронизацией через мьютексы или каналы.

Показать полностью 10

Паника-паника!

Паника-паника! My Little Pony, Ponyart, Princess Celestia, Witchtaunter

Страшно! Очень страшно! Мы не знаем что это такое, если бы мы знали, что это такое, но мы не знаем, что это такое!

Вот вам старт главного двигателя на корабле/теплоходе/пароходе…

Cachoeira da Fumaa - водопад в штате Баия, Бразилия

О МЕТАФИЗИКЕ АЛКОГОЛЯ

О МЕТАФИЗИКЕ АЛКОГОЛЯ Литература, Писатели, Культура, Мужчины и женщины, Россия, Алкоголь, История (наука), Сценарий, Длиннопост

Иосиф Бродский:
— Помню, к Володе Уфлянду пришёл некий американ и принёс бутылку "Beefeater". Это было довольно давно, году в 1959-ом. И вот сидим мы, смотрим на картинку: гвардеец в Тауэре во всех этих красных причиндалах. И тут Уфлянд сделал одно из самых проникновенных замечаний, которые я помню. Он сказал: "Знаешь, Иосиф, вот мы сейчас затарчиваем от этой картинки. А они там, на Западе, затарчивают, наверное, от отсутствия какой бы то ни было картинки на нашей водяре".
Соломон Волков:
— Так вроде бы на русской водке есть картинка — небоскрёб какой-то сталинский!
Иосиф Бродский:
— Это вы говорите о гостинице "Москва" на этикетке "Столичной". А вот на водке просто, "Московская" она называлась, была такая бело-зелёная наклейка: ничего абстрактней представить себе, на мой взгляд, невозможно. И когда смотришь на это зелёное с белым, на эти чёрные буквы — особенно в состоянии подпития, — то очень сильно балдеешь, половинка зелёного, а дальше белое, да? Такой горизонт, иероглиф бесконечности. <...>

Кого на какие мысли наводит эта цитата из книги Соломона Волкова "Диалоги с Иосифом Бродским", а мне напоминает о неожиданном и мгновенном осознании: "Я взрослый!"

О МЕТАФИЗИКЕ АЛКОГОЛЯ Литература, Писатели, Культура, Мужчины и женщины, Россия, Алкоголь, История (наука), Сценарий, Длиннопост

В первой половине 1980-х был я чуть старше Бродского и чуть младше Уфлянда в 1959-м — лет двадцати. Случаев почувствовать себя взрослым хватало и раньше, пускай без достаточных оснований. Но отчётливая мысль промелькнула внезапно, в компании троих давних товарищей, в ресторане "Вечерний" на углу Литейного проспекта с улицей Некрасова — в двух шагах от дома Бродского.
Мы зашли, сели за стол с белой крахмальной скатертью, не спеша выпили "сабониса" — бутылку "Московской" 0.75 л, названную в честь нашего ровесника, огромного литовского баскетболиста, — хорошо закусили, слегка пофлиртовали с очаровательной официанткой Наилей, без проблем расплатились из собственных заработков и перед самым закрытием разошлись.
Водку я тогда не слишком жаловал, позже пил её довольно редко, сейчас тем более предпочитаю другие напитки...
...но через тридцать лет после той посиделки дал имя Наиля героине-официантке в сценарии 12-серийного фильма "Гостиница "Россия"", и ещё десять лет спустя продолжаю вспоминать вроде бы ничем не примечательное событие.
Вот ведь как. Даже снова водки захотелось.

О МЕТАФИЗИКЕ АЛКОГОЛЯ Литература, Писатели, Культура, Мужчины и женщины, Россия, Алкоголь, История (наука), Сценарий, Длиннопост
Показать полностью 3

Облачно-цветочное

Облачно-цветочное Фотография, Природа, Кавказские Минеральные Воды, Пейзаж, Бештау, Горы, Цветы, Облака
Показать полностью 1

Сильный и независимый... :)

Сильный и независимый... :) Короткопост, Юмор, Кот, Домашние животные, Мобильная фотография

Сразу с тремя девушками. О_о

Показать полностью 1

Палево! А может и нет...

Когда подумал, что никто не заметит...

Палево! А может и нет... Кот, Мейн-кун, Палево, Любовь, Пофигизм, И че, Домашние животные, Фотография

Хотя и так видно, что похрен...

Палево! А может и нет... Кот, Мейн-кун, Палево, Любовь, Пофигизм, И че, Домашние животные, Фотография
Показать полностью 2
Мои подписки
Подписывайтесь на интересные вам теги, сообщества, авторов — и читайте свои любимые темы в этой ленте.
Чтобы добавить подписку, нужно авторизоваться.

Отличная работа, все прочитано! Выберите