🔍 Вопрос: что выведет этот код?
---
✅ Ответ: Вывод программы будет зависеть от порядка итерации по ключам в исходной мапе m.
В Go порядок итерации по элементам map не гарантируется и может меняться от запуска к запуску.
Рассмотрим возможные варианты развития событий, предполагая два наиболее вероятных порядка итерации:
Сценарий 1: Порядок итерации - "a", "b"
Итерация 1:
k становится "a".
keys становится ["a"].
Удаляется элемент с ключом "a" из m. m теперь {"b": 2}.
Добавляется элемент {"z": 0} в m. m теперь {"b": 2, "z": 0}.
Итерация 2:
k становится "b".
keys становится ["a", "b"].
Удаляется элемент с ключом "b" из m. m теперь {"z": 0}.
Добавляется элемент {"z": 1} в m (перезаписывая предыдущее значение). m теперь {"z": 1}.
В этом случае вывод будет:
map: map["z":1]
keys: [a b]
Сценарий 2: Порядок итерации - "b", "a"
Итерация 1:
k становится "b".
keys становится ["b"].
Удаляется элемент с ключом "b" из m. m теперь {"a": 1}.
Добавляется элемент {"z": 0} в m. m теперь {"a": 1, "z": 0}.
Итерация 2:
k становится "a".
keys становится ["b", "a"].
Удаляется элемент с ключом "a" из m. m теперь {"z": 0}.
Добавляется элемент {"z": 1} в m (перезаписывая предыдущее значение). m теперь {"z": 1}.
В этом случае вывод будет:
map: map["z":1]
keys: [b a]
Итог:
Гарантируется, что:
Значение в мапе m после выполнения цикла всегда будет map["z":1]. Это происходит потому, что в каждой итерации старое значение ключа "z" перезаписывается.
Слайс keys будет содержать ключи исходной мапы в том порядке, в котором они были получены при первой итерации. Это может быть ["a", "b"] или ["b", "a"], или какой-либо другой порядок, если мапа была больше.
Поэтому возможные варианты вывода:
Вариант 1:
map: map["z":1]
keys: [a b]
Вариант 2:
map: map["z":1]
keys: [b a]
Какой именно вариант вы увидите при запуске, предсказать невозможно из-за недетерминированного порядка итерации по map в Go.
package main
import (
"fmt"
)
func main() {
m := map[string]int{"a": 1, "b": 2}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
for i := range keys {
delete(m, keys[i])
m["z"] = i
}
fmt.Println("map:", m)
fmt.Println("keys:", keys)
}
---
✅ Ответ:
В Go порядок итерации по элементам map не гарантируется и может меняться от запуска к запуску.
Рассмотрим возможные варианты развития событий, предполагая два наиболее вероятных порядка итерации:
Сценарий 1: Порядок итерации - "a", "b"
Итерация 1:
k становится "a".
keys становится ["a"].
Удаляется элемент с ключом "a" из m. m теперь {"b": 2}.
Добавляется элемент {"z": 0} в m. m теперь {"b": 2, "z": 0}.
Итерация 2:
k становится "b".
keys становится ["a", "b"].
Удаляется элемент с ключом "b" из m. m теперь {"z": 0}.
Добавляется элемент {"z": 1} в m (перезаписывая предыдущее значение). m теперь {"z": 1}.
В этом случае вывод будет:
map: map["z":1]
keys: [a b]
Сценарий 2: Порядок итерации - "b", "a"
Итерация 1:
k становится "b".
keys становится ["b"].
Удаляется элемент с ключом "b" из m. m теперь {"a": 1}.
Добавляется элемент {"z": 0} в m. m теперь {"a": 1, "z": 0}.
Итерация 2:
k становится "a".
keys становится ["b", "a"].
Удаляется элемент с ключом "a" из m. m теперь {"z": 0}.
Добавляется элемент {"z": 1} в m (перезаписывая предыдущее значение). m теперь {"z": 1}.
В этом случае вывод будет:
map: map["z":1]
keys: [b a]
Итог:
Гарантируется, что:
Значение в мапе m после выполнения цикла всегда будет map["z":1]. Это происходит потому, что в каждой итерации старое значение ключа "z" перезаписывается.
Слайс keys будет содержать ключи исходной мапы в том порядке, в котором они были получены при первой итерации. Это может быть ["a", "b"] или ["b", "a"], или какой-либо другой порядок, если мапа была больше.
Поэтому возможные варианты вывода:
Вариант 1:
map: map["z":1]
keys: [a b]
Вариант 2:
map: map["z":1]
keys: [b a]
Какой именно вариант вы увидите при запуске, предсказать невозможно из-за недетерминированного порядка итерации по map в Go.
🧠 Задача на Go (1.22+)
Что выведет этот код?
A.
c 3
b 2
a 1
done
B.
done
a 1
b 2
c 3
C.
done
c 3
b 2
a 1
D.
a 3
a 3
a 3
done
Почему A? — в Go 1.22 range-переменные и создаются на каждом шаге заново, а defer захватывает копии, не ссылки.
package main
import "fmt"
func main() {
m = map[string]int{"a": 1, "b": 2, "c": 3}
defer fmt.Println("done")
for k, v := range m {
defer fmt.Println(k, v)
}
}
Что выведет этот код?
A.
c 3
b 2
a 1
done
B.
done
a 1
b 2
c 3
C.
done
c 3
b 2
a 1
D.
a 3
a 3
a 3
done
k
v
🐹 Задача с подвохом: Generics и интерфейсы (Go 1.24)
Условие:
Что выведет следующий код и почему?
❓ Вопрос:
Что будет напечатано? Почему результат может удивить даже опытных Go-разработчиков?
🔍 Разбор:
Этот код использует generics и проверяет, реализует ли интерфейс .
Пошагово:
1️⃣
Тип реализует интерфейс через метод:
```go
func (m MyInt) String() string { ... }
```
2️⃣
- здесь типа .
- В строке:
- Приведение типа успешно, потому что реализует .
- Печатается:
```
Stringer: MyInt: 5
```
3️⃣
- Тип здесь .
- не реализует .
- Приведение не срабатывает (ok == false).
- Печатается:
```
Default: 10
```
✅ Ожидаемый вывод:
```
Stringer: MyInt: 5
Default: 10
```
💥 Подвох:
- Даже если — это просто , метод делает его другим типом, который реализует интерфейс.
- Многие забывают, что встроенные типы (int, string, и т.д.) никогда не реализуют интерфейсы напрямую, даже если они похожи на пользовательские типы.
- Ключевой момент: оборачивает значение в интерфейс{}, поэтому приведение типа срабатывает только если исходный тип действительно реализует интерфейс.
🆕 Почему это актуально для Go 1.24:
Generics в Go 1.22+ улучшились по типовой проверке и стали более гибкими. Go 1.24 сделал generics более стабильными и оптимизированными, но все еще важно понимать различия между типами и интерфейсами, особенно при работе с и приведения типов внутри параметризованных функций.
🛡️ Лайфхак:
Если вы хотите печатать всё одинаково для типов с , можно сделать generic-констрейн с интерфейсом:
```go
func printValue2[T fmt.Stringer](val T) {
fmt.Println("Stringer (generic):", val.String())
}
```
✅ Вывод:
Generics упрощают код, но внимательность к типам и их интерфейсам критически важна. Даже небольшие различия между и влияют на поведение программы.
Условие:
Что выведет следующий код и почему?
package main
import (
"fmt"
)
type Stringer interface {
String() string
}
type MyInt int
func (m MyInt) String() string {
return fmt.Sprintf("MyInt: %d", m)
}
func printValue[T any](val T) {
var s Stringer
if v, ok := any(val).(Stringer); ok {
s = v
}
if s != nil {
fmt.Println("Stringer:", s.String())
} else {
fmt.Println("Default:", val)
}
}
func main() {
var x MyInt = 5
printValue(x)
printValue(10)
}
❓ Вопрос:
Что будет напечатано? Почему результат может удивить даже опытных Go-разработчиков?
🔍 Разбор:
Этот код использует generics и проверяет, реализует ли
val
Stringer
Пошагово:
1️⃣
var x MyInt = 5
Тип
MyInt
Stringer
```go
func (m MyInt) String() string { ... }
```
2️⃣
printValue(x)
-
val
MyInt
- В строке:
any(val).(Stringer)
- Приведение типа успешно, потому что
MyInt
Stringer
- Печатается:
```
Stringer: MyInt: 5
```
3️⃣
printValue(10)
- Тип
val
int
-
int
Stringer
- Приведение
any(val).(Stringer)
- Печатается:
```
Default: 10
```
✅ Ожидаемый вывод:
```
Stringer: MyInt: 5
Default: 10
```
💥 Подвох:
- Даже если
MyInt
int
String()
- Многие забывают, что встроенные типы (int, string, и т.д.) никогда не реализуют интерфейсы напрямую, даже если они похожи на пользовательские типы.
- Ключевой момент:
any(val)
🆕 Почему это актуально для Go 1.24:
Generics в Go 1.22+ улучшились по типовой проверке и стали более гибкими. Go 1.24 сделал generics более стабильными и оптимизированными, но все еще важно понимать различия между типами и интерфейсами, особенно при работе с
any
🛡️ Лайфхак:
Если вы хотите печатать всё одинаково для типов с
String()
```go
func printValue2[T fmt.Stringer](val T) {
fmt.Println("Stringer (generic):", val.String())
}
```
✅ Вывод:
Generics упрощают код, но внимательность к типам и их интерфейсам критически важна. Даже небольшие различия между
int
MyInt
🧠 Хитрая задача на Go: "Петля Блуждающего Робота"
Представь: у тебя есть робот, который бродит по бесконечной двумерной решетке. Он выполняет команды из строки:
-
-
-
Робот стартует из точки
👉 Зациклится ли движение робота?
(т.е. вернётся ли он в исходную точку или навсегда останется в замкнутом цикле)
📌 Примеры:
🧩 Подвох задачи:
На первый взгляд кажется, что нужно бесконечно симулировать команды. Но на самом деле всё решается за один проход строки!
Важно: если после одного прохода робот:
- вернулся в (0,0) или
- сменил направление — значит, будет цикл.
✅ Go-реализация:
```go
func isLooping(commands string) bool {
dirs := [][2]int{{0,1}, {1,0}, {0,-1}, {-1,0}} // север, восток, юг, запад
x, y, d := 0, 0, 0
for _, c := range commands {
switch c {
case 'G':
x += dirs[d][0]
y += dirs[d][1]
case 'L':
d = (d + 3) % 4
case 'R':
d = (d + 1) % 4
}
}
return (x == 0 && y == 0) || d != 0
}
```
🎯 Отличная задача для собеседования: она проверяет
• понимание направления и симуляции
• знание работы с векторами
• умение заменить бесконечный цикл на математический анализ
@golangtests
Представь: у тебя есть робот, который бродит по бесконечной двумерной решетке. Он выполняет команды из строки:
-
'G'
— идти вперед-
'L'
— повернуть налево (90°)-
'R'
— повернуть направо (90°)Робот стартует из точки
(0, 0)
и смотрит на север. Команды повторяются бесконечно. Нужно определить:👉 Зациклится ли движение робота?
(т.е. вернётся ли он в исходную точку или навсегда останется в замкнутом цикле)
📌 Примеры:
isLooping("GLGLGLG") => true // движется по квадрату
isLooping("GG") => false // уходит навсегда
🧩 Подвох задачи:
На первый взгляд кажется, что нужно бесконечно симулировать команды. Но на самом деле всё решается за один проход строки!
Важно: если после одного прохода робот:
- вернулся в (0,0) или
- сменил направление — значит, будет цикл.
✅ Go-реализация:
```go
func isLooping(commands string) bool {
dirs := [][2]int{{0,1}, {1,0}, {0,-1}, {-1,0}} // север, восток, юг, запад
x, y, d := 0, 0, 0
for _, c := range commands {
switch c {
case 'G':
x += dirs[d][0]
y += dirs[d][1]
case 'L':
d = (d + 3) % 4
case 'R':
d = (d + 1) % 4
}
}
return (x == 0 && y == 0) || d != 0
}
```
🎯 Отличная задача для собеседования: она проверяет
• понимание направления и симуляции
• знание работы с векторами
• умение заменить бесконечный цикл на математический анализ
@golangtests
🧠 Хитрая задача на Go (v1.22+) — алгоритмы, циклы и подвох в логике
📌 Задача: "Подряд идущие квадраты"
Дано: массив из N положительных целых чисел
Нужно найти максимальную длину подмассива, где все элементы — квадраты подряд идущих натуральных чисел.
Например,
🎯 Формат функции:
✅ Пример:
🧩 Подвох:
- Часто пытаются сравнивать разности или применять хэшмапы — но это ошибка
- Нужно восстановить корни чисел (`sqrt`) и убедиться, что они натуральные и идут подряд
- Важно использовать math.Sqrt и быть осторожным с плавающей точкой (`float64`)
- Также важно не выходить за границы slice
💡 Подсказка:
```go
import "math"
func isSquare(n int) (int, bool) {
root := int(math.Sqrt(float64(n)))
return root, root*root == n
}
```
🛠 **Что проверяет задача:**
• Умение работать с нецелыми корнями
• Алгоритмы "двойного указателя" (две границы окна)
• Понимание строгой проверки на натуральные числа
• Внимание к float64-погрешности и типам
📌 Задача: "Подряд идущие квадраты"
Дано: массив из N положительных целых чисел
[]int
, например:
[]int{1, 4, 9, 16, 25, 36, 50, 64, 81}
Нужно найти максимальную длину подмассива, где все элементы — квадраты подряд идущих натуральных чисел.
Например,
[4, 9, 16, 25]
— это квадраты 2², 3², 4², 5² → длина = 4 [50, 64]
— не подходят, так как 50 не является точным квадратом.🎯 Формат функции:
func MaxConsecutiveSquares(nums []int) int
✅ Пример:
input := []int{1, 4, 9, 16, 25, 36, 50, 64, 81}
fmt.Println(MaxConsecutiveSquares(input)) // 👉 6
🧩 Подвох:
- Часто пытаются сравнивать разности или применять хэшмапы — но это ошибка
- Нужно восстановить корни чисел (`sqrt`) и убедиться, что они натуральные и идут подряд
- Важно использовать math.Sqrt и быть осторожным с плавающей точкой (`float64`)
- Также важно не выходить за границы slice
💡 Подсказка:
`
import "math"
func isSquare(n int) (int, bool) {
root := int(math.Sqrt(float64(n)))
return root, root*root == n
}
```
🛠 **Что проверяет задача:**
• Умение работать с нецелыми корнями
• Алгоритмы "двойного указателя" (две границы окна)
• Понимание строгой проверки на натуральные числа
• Внимание к float64-погрешности и типам
Forwarded from Golang
🔐 Boulder — реализация ACME-совместимого центра сертификации, автоматически проверяющего владение доменом и выпускающего TLS-сертификаты. Именно на нём работает Let’s Encrypt, бесплатно обеспечивая шифрование для миллионов сайтов.
Проект разделён на компоненты: Web Frontend, Registration Authority, Certificate Authority и другие, что позволяет изолировать критичные части системы. Внутри — строгая логика на основе объектов ACME: аккаунтов, авторизаций и сертификатов. Для связи между модулями используется gRPC, а для разработки — Docker-окружение с полным набором зависимостей.
🤖 GitHub
@golang_google
Проект разделён на компоненты: Web Frontend, Registration Authority, Certificate Authority и другие, что позволяет изолировать критичные части системы. Внутри — строгая логика на основе объектов ACME: аккаунтов, авторизаций и сертификатов. Для связи между модулями используется gRPC, а для разработки — Docker-окружение с полным набором зависимостей.
🤖 GitHub
@golang_google
🧠 Задача для Go-разработчиков: "Подозрительно сбалансированное число"
Условие
Найди наименьшее положительное целое число
1. Цифры
2.
3.
Найди такое число, напиши код, который это делает эффективно, и объясни, почему перебор — не самый лучший способ.
Пример:
---
📌 Реализуй функцию:
---
Подсказка:
Задача решается быстрее, если строить палиндромы по шаблону, а не перебирать все числа подряд.
---
🔍 Решение:
```go
package main
import (
"fmt"
"strconv"
)
func isBalanced(digits []int) bool {
n := len(digits)
for i := 1; i < n; i++ {
left := digits[:i]
right := digits[i:]
sumL, sumR := 0, 0
for _, d := range left {
sumL += d
}
for _, d := range right {
sumR += d
}
if sumL == sumR {
return true
}
}
return false
}
func FindBalancedPalindrome() int {
for i := 11; ; i++ {
if i%10 == 0 {
continue
}
s := strconv.Itoa(i)
rev := reverse(s)
if s != rev {
continue
}
digits := make([]int, len(s))
for idx, ch := range s {
digits[idx] = int(ch - '0')
}
if isBalanced(digits) {
return i
}
}
}
func reverse(s string) string {
r := []rune(s)
for i := 0; i < len(r)/2; i++ {
r[i], r[len(r)-1-i] = r[len(r)-1-i], r[i]
}
return string(r)
}
func main() {
fmt.Println("Ответ:", FindBalancedPalindrome())
}
```
✅ Объяснение:
1. Мы перебираем **только палиндромы**, игнорируя шум.
2. Для каждого палиндрома проверяем, можно ли разделить цифры на две группы с равной суммой.
3. Проверка выполняется за `O(n)` на каждое число, где `n` — длина числа.
Такой подход эффективнее полного перебора от 1 до бесконечности.
🔥 Эта задача проверяет:
• знание работы с цифрами и строками
• умение писать генераторы палиндромов
• понимание оптимизации перебора
• грамотное разбиение массива на подмассивы
• работу со строками и рунами в Go
Условие
Найди наименьшее положительное целое число
N
, для которого выполняются все три условия:1. Цифры
N
можно разделить на две группы, сумма которых равна (например: 3213
→ `3+2 = 1+3`).2.
N
является палиндромом (читается одинаково слева направо и справа налево).3.
N
не делится на 10 (исключаем очевидные трюки вроде "1001").Найди такое число, напиши код, который это делает эффективно, и объясни, почему перебор — не самый лучший способ.
Пример:
3213 -> цифры: [3,2,1,3]
Разделение: [3,2] и [1,3] → сумма 5 и 4 → не равны → не подходит
1331 -> [1,3,3,1] → [1,3]=4, [3,1]=4 → ✅ подходит (и палиндром, и сбалансирован)
---
📌 Реализуй функцию:
func FindBalancedPalindrome() int
---
Подсказка:
Задача решается быстрее, если строить палиндромы по шаблону, а не перебирать все числа подряд.
---
🔍 Решение:
```go
package main
import (
"fmt"
"strconv"
)
func isBalanced(digits []int) bool {
n := len(digits)
for i := 1; i < n; i++ {
left := digits[:i]
right := digits[i:]
sumL, sumR := 0, 0
for _, d := range left {
sumL += d
}
for _, d := range right {
sumR += d
}
if sumL == sumR {
return true
}
}
return false
}
func FindBalancedPalindrome() int {
for i := 11; ; i++ {
if i%10 == 0 {
continue
}
s := strconv.Itoa(i)
rev := reverse(s)
if s != rev {
continue
}
digits := make([]int, len(s))
for idx, ch := range s {
digits[idx] = int(ch - '0')
}
if isBalanced(digits) {
return i
}
}
}
func reverse(s string) string {
r := []rune(s)
for i := 0; i < len(r)/2; i++ {
r[i], r[len(r)-1-i] = r[len(r)-1-i], r[i]
}
return string(r)
}
func main() {
fmt.Println("Ответ:", FindBalancedPalindrome())
}
✅ Объяснение:
1. Мы перебираем **только палиндромы**, игнорируя шум.
2. Для каждого палиндрома проверяем, можно ли разделить цифры на две группы с равной суммой.
3. Проверка выполняется за `O(n)` на каждое число, где `n` — длина числа.
Такой подход эффективнее полного перебора от 1 до бесконечности.
🔥 Эта задача проверяет:
• знание работы с цифрами и строками
• умение писать генераторы палиндромов
• понимание оптимизации перебора
• грамотное разбиение массива на подмассивы
• работу со строками и рунами в Go
❓ Когда не получится применить бинарный поиск
Anonymous Quiz
9%
Если элементы массива — строки, а не числа
10%
Если в массиве есть дубликаты
74%
Если данные не отсортированы
7%
Узнать ответ
🐹 Задача для Go 1.21+: «Контекст отменён, но горутина продолжает работу»
📌 Актуально для: Go 1.21 и новее (введён `context.WithCancelCause`)
🎯 Цель: Понять, почему горутина не завершилась по отменённому контексту
📍 Ситуация:
Ты используешь контекст для управления жизненным циклом горутины. В Go 1.21 ты решил использовать
🔍 Ты ожидаешь, что горутина завершится и выведет:
Но вместо этого — программа завершилась без вывода. Почему?
🧩 Вопросы:
1. Почему
2. Что изменилось в
3. Как безопасно читать причину отмены?
4. Как изменить
5. Почему важно не блокироваться на `ctx.Done()`, если возможна гонка?
🛠 Решение:
🔸 В Go 1.21 есть `context.WithCancelCause`, который позволяет задавать причину отмены.
Но `context.Cause(ctx)` вернёт `nil`, **если ты используешь `context.WithCancel`**, либо, если `ctx.Done()` не был срабатывающим.
🔸 В этом коде `worker(ctx)` запускается и сразу блокируется на:
<-ctx.Done()
Но если отмена происходит **до** того, как `worker` успел начать слушать `ctx.Done()`, и ты используешь старую `WithCancel`, `context.Cause` вернёт `nil`.
🔸 **Правильный способ:**
Убедись, что `context.WithCancelCause` действительно используется и `ctx.Done()` слушается вовремя.
Для Go 1.21+ пример рабочий:
🔸 Альтернатива для старых версий Go (<1.21):
ctx, cancel := context.WithCancel(context.Background())
...
fmt.Println("Worker stopped:", ctx.Err()) // вместо Cause
📌 Вывод:
Начиная с Go 1.21, `context.WithCancelCause` даёт более точный контроль за причинами отмены. Но горутины всё равно должны явно проверять `ctx.Done()` через `select`, иначе отмена может пройти незаметно.
📌 Актуально для: Go 1.21 и новее (введён `context.WithCancelCause`)
🎯 Цель: Понять, почему горутина не завершилась по отменённому контексту
📍 Ситуация:
Ты используешь контекст для управления жизненным циклом горутины. В Go 1.21 ты решил использовать
context.WithCancelCause
:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
go worker(ctx)
time.Sleep(1 * time.Second)
cancel(fmt.Errorf("manual stop"))
time.Sleep(2 * time.Second)
}
func worker(ctx context.Context) {
<-ctx.Done()
fmt.Println("Worker stopped:", context.Cause(ctx))
}
🔍 Ты ожидаешь, что горутина завершится и выведет:
Worker stopped: manual stop
Но вместо этого — программа завершилась без вывода. Почему?
🧩 Вопросы:
1. Почему
worker
не печатает "Worker stopped: ..."
? 2. Что изменилось в
context.WithCancelCause
по сравнению с WithCancel
? 3. Как безопасно читать причину отмены?
4. Как изменить
worker
, чтобы он корректно завершался? 5. Почему важно не блокироваться на `ctx.Done()`, если возможна гонка?
🛠 Решение:
Но `context.Cause(ctx)` вернёт `nil`, **если ты используешь `context.WithCancel`**, либо, если `ctx.Done()` не был срабатывающим.
🔸 В этом коде `worker(ctx)` запускается и сразу блокируется на:
🔸 **Правильный способ:**
Убедись, что `context.WithCancelCause` действительно используется и `ctx.Done()` слушается вовремя.
Для Go 1.21+ пример рабочий:
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker stopped:", context.Cause(ctx))
return
case <-time.After(100 * time.Millisecond):
fmt.Println("Working...")
}
}
}
🔸 Альтернатива для старых версий Go (<1.21):
...
fmt.Println("Worker stopped:", ctx.Err()) // вместо Cause
📌 Вывод: