The classic programming puzzle, tackled a few different ways.
Test and lint:
pip install -r requirements.txt
black --check .
flake8 *.py
mypy *.py
pytest
Example usage:
python3 -m naive 10
python3 -m strings 100
python3 -m cycles -h
import argparse | |
from collections.abc import Generator | |
from typing import Callable, List, Optional | |
class Cli: | |
"""A uniform interface for all FizzBuzz methods. | |
Examples | |
-------- | |
>>> def fizzbuzz(n: int) -> Generator[str, None, None]: | |
... yield from range(1, n + 1) | |
>>> cli = Cli(func=fizzbuzz) | |
>>> cli(["10"]) | |
1 2 3 4 5 6 7 8 9 10 | |
""" | |
def __init__(self, func: Callable[[int], Generator[str, None, None]]): | |
"""Create a new CLI instance wrapping a Fizzbuzz implementation. | |
Parameters | |
---------- | |
func | |
A generator function implementing FizzBuzz. | |
Attributes | |
---------- | |
func: | |
A generator function implementating FizzBuzz. | |
parser: | |
The prepared argparse instance. | |
""" | |
self.func = func | |
self.parser = argparse.ArgumentParser( | |
description="A FizzBuzz program." | |
) | |
self.parser.add_argument( | |
"n", type=int, help="Run FizzBuzz up to n (inclusive)." | |
) | |
def __call__(self, args: Optional[List[str]] = None): | |
"""Call the CLI. | |
Outputs each element in the FizzBuzz sequence on a newline. | |
Parameters | |
---------- | |
args | |
Optional list of arguments to override ``sys.argv`` (for testing | |
purposes, for instance). | |
""" | |
parsed = self.parser.parse_args(args=args) | |
for i in self.func(parsed.n): | |
print(i) |
from collections.abc import Generator | |
from itertools import cycle | |
def fizzbuzz(n: int) -> Generator[str, None, None]: | |
""" | |
An FizzBuzz implementation which avoids division. | |
Uses cycles to produce the output without having to check the modulus. | |
Parameters | |
---------- | |
n : int | |
Run the FizzBuzz program between 1 and n (inclusive). | |
Examples | |
-------- | |
>>> list(fizzbuzz(20)) | |
['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', | |
'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz'] | |
""" | |
fizzes = cycle(["", "", "Fizz"]) | |
buzzes = cycle(["", "", "", "", "Buzz"]) | |
for f, b, i in zip(fizzes, buzzes, range(1, n + 1)): | |
yield f + b or str(i) | |
if __name__ == "__main__": | |
from cli import Cli | |
cli = Cli(fizzbuzz) | |
cli() |
from collections.abc import Generator | |
from itertools import cycle | |
from typing import Tuple | |
class FizzBuzz: | |
""" | |
A general purpose FizzBuzz generator. | |
Examples | |
-------- | |
First, a simple a example where every fourth integer is replace with | |
"Boar": | |
>>> fizzbuzz = FizzBuzz(("Boar", 4)) | |
>>> list(fizzbuzz(10)) | |
['1', '2', '3', 'Boar', '5', '6', '7', 'Boar', '9', '10'] | |
Then the classic FizzBuzz: | |
>>> fizzbuzz = FizzBuzz(("Fizz", 3), ("Buzz", 5)) | |
>>> list(fizzbuzz(20)) | |
['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', | |
'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz'] | |
""" | |
def __init__(self, *args: Tuple[str, int]): | |
"""Create a new FizzBuzz generator. | |
Parameters | |
---------- | |
*args | |
Pairs of word/integer to replace. See example. | |
""" | |
self.words = args | |
def __call__(self, n: int) -> Generator[str, None, None]: | |
"""Run FizzBuzz. | |
Parameters | |
---------- | |
n : int | |
Run the FizzBuzz program between 1 and n (inclusive). | |
""" | |
cycles = [ | |
cycle([""] * (divisor - 1) + [word]) | |
for word, divisor in self.words | |
] | |
for *step, i in zip(*cycles, range(1, n + 1)): | |
yield "".join(step) or str(i) | |
fizzbuzz = FizzBuzz(("Fizz", 3), ("Buzz", 5)) | |
if __name__ == "__main__": | |
from cli import Cli | |
cli = Cli(fizzbuzz) | |
cli() |
from collections.abc import Generator | |
def fizzbuzz(n: int) -> Generator[str, None, None]: | |
""" | |
A naive FizzBuzz implementation. | |
Uses if/else conditions to decide what to output at each stage. | |
Parameters | |
---------- | |
n : int | |
Run the FizzBuzz program between 1 and n (inclusive). | |
Examples | |
-------- | |
>>> list(fizzbuzz(20)) | |
['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', | |
'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz'] | |
""" | |
for i in range(1, n + 1): | |
if i % 15 == 0: | |
yield "FizzBuzz" | |
elif i % 5 == 0: | |
yield "Buzz" | |
elif i % 3 == 0: | |
yield "Fizz" | |
else: | |
yield str(i) | |
if __name__ == "__main__": | |
from cli import Cli | |
cli = Cli(fizzbuzz) | |
cli() |
[tool.black] | |
line-length = 79 | |
target-version = ['py310'] | |
[tool.pytest.ini_options] | |
addopts = "--doctest-modules" | |
doctest_optionflags = "NORMALIZE_WHITESPACE" |
black | |
flake8 | |
mypy | |
pytest |
from collections.abc import Generator | |
def fizzbuzz(n: int) -> Generator[str, None, None]: | |
""" | |
A one-liner FizzBuzz implementation. | |
Builds an output string based on conditionals. | |
Parameters | |
---------- | |
n : int | |
Run the FizzBuzz program between 1 and n (inclusive). | |
Examples | |
-------- | |
>>> list(fizzbuzz(20)) | |
['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', | |
'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz'] | |
""" | |
for i in range(1, n + 1): | |
yield "Fizz" * (i % 3 == 0) + "Buzz" * (i % 5 == 0) or str(i) | |
if __name__ == "__main__": | |
from cli import Cli | |
cli = Cli(fizzbuzz) | |
cli() |