> Как реализовать коллекцию для хранения связи между customerId, accountNumber и статусом ареста счета без использования объектов? (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Альфа-банк
Стек: Go
> Пример ответа
Короткий ответ
Для хранения связи между customerId, accountNumber и статусом ареста без объектов в Go используем три параллельных среза (slice) или map с составным ключом. Оптимальный вариант - map[string]int, где ключ - конкатенация customerId и accountNumber через разделитель, значение - статус ареста. Это даёт O(1) доступ и не требует создания структур. Для batch-операций подойдут параллельные срезы с индексной синхронизацией.
Подробное объяснение
В Go отсутствуют объекты в классическом ООП-смысле, но есть структуры. Если задача - избежать структур, используем примитивные типы. Основные подходы:
-
Map с составным ключом:
map[string]int. Ключ формируется какfmt.Sprintf("%d:%s", customerId, accountNumber)или черезstrconv.Itoa(customerId) + ":" + accountNumber. Плюсы: быстрый доступ, простота. Минусы: конкатенация строк на каждую операцию, риск коллизий при неправильном разделителе. -
Параллельные срезы:
customerIDs []int,accountNumbers []string,statuses []int. Индекс - неявная связь. Плюсы: компактность, кеш-локальность. Минусы: сложность удаления, риск рассинхронизации. -
Вложенные map:
map[int]map[string]int- customerId → accountNumber → статус. Плюсы: естественная группировка по customerId. Минусы: избыточность памяти, сложность обновления. -
Битовые маски или массивы фиксированной длины - если домен значений ограничен и известен заранее.
Для senior-позиции важно обсудить trade-off между скоростью доступа, памятью и сложностью поддержки. В production чаще используют map с составным ключом, так как он даёт баланс между простотой и производительностью.
На практике
Выбор зависит от паттерна доступа:
- Если нужен частый поиск по паре (customerId, accountNumber) - map с составным ключом.
- Если нужны частые итерации по всем записям или сортировка - параллельные срезы.
- Если нужны операции по всем счетам одного customerId - вложенные map.
Для статуса ареста используем int с константами (0 - нет ареста, 1 - арест, 2 - частичный арест). Это быстрее и экономит память по сравнению со string.
Важно продумать потокобезопасность: если коллекция используется конкурентно, нужен sync.RWMutex или шардирование map.
Пример кода
GOpackage mainimport ("fmt""strconv""sync")type ArrestStatus intconst (StatusNoArrest ArrestStatus = iotaStatusArrestedStatusPartiallyArrested)// Вариант 1: map с составным ключомtype ArrestRegistry struct {mu sync.RWMutexdata map[string]ArrestStatus}func NewArrestRegistry() *ArrestRegistry {return &ArrestRegistry{data: make(map[string]ArrestStatus),}}func key(customerID int, accountNumber string) string {return strconv.Itoa(customerID) + ":" + accountNumber}func (r *ArrestRegistry) Set(customerID int, accountNumber string, status ArrestStatus) {r.mu.Lock()defer r.mu.Unlock()r.data[key(customerID, accountNumber)] = status}func (r *ArrestRegistry) Get(customerID int, accountNumber string) (ArrestStatus, bool) {r.mu.RLock()defer r.mu.RUnlock()status, ok := r.data[key(customerID, accountNumber)]return status, ok}// Вариант 2: параллельные срезы (без потокобезопасности для простоты)type SliceRegistry struct {customerIDs []intaccountNums []stringstatuses []ArrestStatus}func (s *SliceRegistry) Append(customerID int, accountNum string, status ArrestStatus) {s.customerIDs = append(s.customerIDs, customerID)s.accountNums = append(s.accountNums, accountNum)s.statuses = append(s.statuses, status)}func (s *SliceRegistry) Find(customerID int, accountNum string) (ArrestStatus, bool) {for i := range s.customerIDs {if s.customerIDs[i] == customerID && s.accountNums[i] == accountNum {return s.statuses[i], true}}return StatusNoArrest, false}func main() {reg := NewArrestRegistry()reg.Set(123, "ACC-001", StatusArrested)if status, ok := reg.Get(123, "ACC-001"); ok {fmt.Printf("Статус: %d\n", status)}}
Как отвечать на собеседовании
Начни с уточнения требований: какой паттерн доступа, конкурентность, объём данных. Затем предложи map с составным ключом как базовый вариант, объясни почему. Упомяни альтернативы и их trade-off. Покажи понимание проблем: коллизии ключей, память, скорость. Если спросят про потокобезопасность - предложи RWMutex или шардирование. Для senior важно продемонстрировать системное мышление: не просто код, а обоснование выбора под конкретные условия.
Что проверяет интервьюер
- Понимание структур данных и их сложности.
- Умение выбирать между trade-off (map vs slice vs nested map).
- Знание особенностей Go: отсутствие generics (в старых версиях), работа с примитивами, конкурентность.
- Способность проектировать без ООП-парадигмы.
- Внимание к деталям: разделители в ключах, обработка ошибок, потокобезопасность.
Типичные ошибки
- Использование
fmt.Sprintfв горячем пути - медленно, лучшеstrconv. - Забывают про потокобезопасность при конкурентном доступе.
- Выбирают вложенные map без необходимости - избыточная память.
- Не учитывают коллизии при конкатенации: если customerId и accountNumber могут содержать разделитель.
- Хранят статус как string вместо int - лишние аллокации.
- Не продумывают удаление записей - в map это просто, в срезах сложно.
> Похожие задачи по backend
Как следить за работой оркестратора и какие метрики использовать для проверки корректности
Как задаются стратегии для каждого платежа: через API или иначе
Какой тип ID является последовательным?
Как происходит эвакуация элементов в хэш-таблице?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью