← Назад к вопросам

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

  1. Проверяй делимость на 15 сначала - избегаешь двойной проверки (подход 4)
  2. Используй конкатенацию строк - элегантнее чем if-else цепочки (подход 2)
  3. Избегай premature optimization - для n=100 обычные строки быстро
  4. Делай код расширяемым - подумай о будущих правилах
  5. Пиши тесты - даже для простых задач

Сравнение подходов

ПодходЧитаемостьРасширяемостьПроизводительность
БазовоеСредняяПлохаяХорошая
ОптимизированноеХорошаяХорошаяХорошая
strings.BuilderХорошаяХорошаяОтличная
ФункциональноеХорошаяПлохаяХорошая
ОбобщенноеОтличнаяОтличнаяСредняя

Рекомендация

Используй Решение 2 (оптимизированное) - это золотой стандарт:

  • Просто и понятно
  • Легко расширяется
  • Хорошая производительность
  • Читаемо в production коде