← Назад к вопросам
FizzBuzz
1.8 Middle🔥 201 комментариев
#Основы Go
Условие
Реализуйте классическую задачу FizzBuzz.
Сигнатура
func fizzBuzz(n int) []string
Правила
Для чисел от 1 до n:
- Если число делится на 3, вернуть "Fizz"
- Если число делится на 5, вернуть "Buzz"
- Если число делится на 3 и на 5, вернуть "FizzBuzz"
- Иначе вернуть строковое представление числа
Пример
Вход: n = 15
Выход: ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"]
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
FizzBuzz - полное решение
Описание задачи
FizzBuzz - классическая задача на интервью, которая проверяет базовые навыки:
- Работа с циклами
- Условные операторы
- Строковые операции
- Построение срезов
Несмотря на простоту, задача часто обнаруживает недостатки в кодировании.
Решение 1: Базовое (если-то-иначе)
package main
import (
"fmt"
"strconv"
)
func fizzBuzz(n int) []string {
result := make([]string, n)
for i := 1; i <= n; i++ {
if i%3 == 0 && i%5 == 0 {
// Делится и на 3, и на 5
result[i-1] = "FizzBuzz"
} else if i%3 == 0 {
// Делится только на 3
result[i-1] = "Fizz"
} else if i%5 == 0 {
// Делится только на 5
result[i-1] = "Buzz"
} else {
// Ни на что не делится
result[i-1] = strconv.Itoa(i)
}
}
return result
}
func main() {
result := fizzBuzz(15)
for _, val := range result {
fmt.Println(val)
}
}
// Вывод:
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// 8
// Fizz
// Buzz
// 11
// Fizz
// 13
// 14
// FizzBuzz
Решение 2: Оптимизированное (конкатенация строк)
func fizzBuzzOptimized(n int) []string {
result := make([]string, n)
for i := 1; i <= n; i++ {
s := "" // построение строки
if i%3 == 0 {
s += "Fizz"
}
if i%5 == 0 {
s += "Buzz"
}
// Если строка пустая, значит число не делится ни на что
if s == "" {
s = strconv.Itoa(i)
}
result[i-1] = s
}
return result
}
Преимущества подхода 2:
- Проще расширяется на новые правила (добавь еще один if)
- Более читаемо при большом количестве условий
- Избегаем множественного вложенного if-else
Решение 3: С использованием strings.Builder (для больших n)
import (
"fmt"
"strconv"
"strings"
)
func fizzBuzzBuilder(n int) []string {
result := make([]string, n)
for i := 1; i <= n; i++ {
var sb strings.Builder
if i%3 == 0 {
sb.WriteString("Fizz")
}
if i%5 == 0 {
sb.WriteString("Buzz")
}
if sb.Len() == 0 {
sb.WriteString(strconv.Itoa(i))
}
result[i-1] = sb.String()
}
return result
}
Когда использовать strings.Builder?
- Когда много конкатенаций в цикле
- Когда строки очень большие
- Для production кода с высокой производительностью
Решение 4: Функциональный подход
func fizzBuzzFunc(n int) []string {
result := make([]string, n)
fizzbuzz := func(num int) string {
switch {
case num%15 == 0: // 15 = НОК(3, 5)
return "FizzBuzz"
case num%3 == 0:
return "Fizz"
case num%5 == 0:
return "Buzz"
default:
return strconv.Itoa(num)
}
}
for i := 1; i <= n; i++ {
result[i-1] = fizzbuzz(i)
}
return result
}
Преимущества:
- Логика инкапсулирована в функции
- Легче тестировать отдельно
- Чище основной цикл
Анализ сложности
- Time: O(n) - один проход через числа от 1 до n
- Space: O(n) - результирующий срез размера n (без учета строк)
Каждая операция внутри цикла - O(1):
- Проверка делимости - O(1)
- Преобразование числа в строку - O(log n) (количество цифр)
- Конкатенация строк - O(1) для коротких строк
Расширение: Обобщенный FizzBuzz
type Rule struct {
Divisor int
Word string
}
func fizzBuzzGeneric(n int, rules []Rule) []string {
result := make([]string, n)
for i := 1; i <= n; i++ {
s := ""
for _, rule := range rules {
if i%rule.Divisor == 0 {
s += rule.Word
}
}
if s == "" {
s = strconv.Itoa(i)
}
result[i-1] = s
}
return result
}
func main() {
// Классический FizzBuzz
rules := []Rule{
{3, "Fizz"},
{5, "Buzz"},
}
result := fizzBuzzGeneric(15, rules)
fmt.Println(result)
// Расширенная версия
extRules := []Rule{
{3, "Fizz"},
{5, "Buzz"},
{7, "Wazz"},
}
result = fizzBuzzGeneric(35, extRules)
fmt.Println(result)
}
Тестирование
func TestFizzBuzz(t *testing.T) {
tests := []struct {
n int
expected []string
}{
{
1,
[]string{"1"},
},
{
3,
[]string{"1", "2", "Fizz"},
},
{
5,
[]string{"1", "2", "Fizz", "4", "Buzz"},
},
{
15,
[]string{
"1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz",
"Buzz", "11", "Fizz", "13", "14", "FizzBuzz",
},
},
}
for _, tt := range tests {
result := fizzBuzz(tt.n)
if !slicesEqual(result, tt.expected) {
t.Errorf("fizzBuzz(%d) = %v, want %v", tt.n, result, tt.expected)
}
}
}
func slicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
Best Practices
- Проверяй делимость на 15 сначала - избегаешь двойной проверки (подход 4)
- Используй конкатенацию строк - элегантнее чем if-else цепочки (подход 2)
- Избегай premature optimization - для n=100 обычные строки быстро
- Делай код расширяемым - подумай о будущих правилах
- Пиши тесты - даже для простых задач
Сравнение подходов
| Подход | Читаемость | Расширяемость | Производительность |
|---|---|---|---|
| Базовое | Средняя | Плохая | Хорошая |
| Оптимизированное | Хорошая | Хорошая | Хорошая |
| strings.Builder | Хорошая | Хорошая | Отличная |
| Функциональное | Хорошая | Плохая | Хорошая |
| Обобщенное | Отличная | Отличная | Средняя |
Рекомендация
Используй Решение 2 (оптимизированное) - это золотой стандарт:
- Просто и понятно
- Легко расширяется
- Хорошая производительность
- Читаемо в production коде