メインコンテンツまでスキップ

合成関数からなるプロダクトレベルのFizzBuzz

· 約4分

プロダクトレベルのFizzBuzz

なんか話題になっていた、絶対にソースコードを書き換えたくない人のためのFizzBuzz。

-> Paiza実行結果

自動生成したクラス図

ソースコード

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Union


@dataclass
class OperatorArgsDTO:
number: int
result: str

def __post_init__(self):
if not isinstance(self.number, int):
raise TypeError("number must be an integer")
if not isinstance(self.result, str):
raise TypeError("result must be a string")

def as_entity(self) -> "OperatorArgs":
return OperatorArgs(self)


class OperatorArgs:
def __init__(self, dto: OperatorArgsDTO):
self.number = dto.number
self.result = dto.result

def add_suffix(self, suffix: str) -> None:
self.result += suffix

def __str__(self) -> str:
return f"OperatorArgs({self.number}, {self.result})"


@dataclass(frozen=True)
class OperatorElement:
x: int
suffix: str


class IOperator(ABC):

@abstractmethod
def __call__(self, a: OperatorArgs) -> OperatorArgs: ...


class IComposeAdd(ABC):

def __add__(self, other: "ComposableOperator") -> "ComposableOperator":
return ComposableOperator(self, other)


class ComposableOperator(IComposeAdd, IOperator):

def __init__(self, f: IOperator, g: IOperator):
self.f = f
self.g = g

def __call__(self, a: OperatorArgs) -> OperatorArgs:
return self.g(self.f(a))


class BaseOperator(IComposeAdd, IOperator):
def __init__(self, e: OperatorElement):
self.validate_element(e)
self.e = e

def validate_element(self, a: OperatorArgs) -> None: ...

@property
def x(self):
return self.e.x

@property
def suffix(self):
return self.e.suffix

@abstractmethod
def __call__(self, a: OperatorArgs) -> OperatorArgs:
raise NotImplementedError


class ContainsOperatorImpl(BaseOperator):

def validate_element(self, e: OperatorElement) -> None:
pass

def __call__(self, a: OperatorArgs) -> OperatorArgs:
b = OperatorArgsDTO(a.number, a.result).as_entity()
if str(self.x) in str(a.number):
b.add_suffix(self.suffix)
return b


class ContainsOperator(ContainsOperatorImpl):

def __init__(self, target: int, suffix: str):
super().__init__(OperatorElement(target, suffix))


class DivisionOperatorImpl(BaseOperator):

def validate_element(self, e: OperatorElement) -> None:
if e.x == 0:
raise ValueError("x must be a non-zero integer")

def __call__(self, a: OperatorArgs) -> OperatorArgs:
b = OperatorArgsDTO(a.number, a.result).as_entity()
if a.number % self.x == 0:
b.add_suffix(self.suffix)
return b


class DivisionOperator(DivisionOperatorImpl):

def __init__(self, divisor: int, suffix: str):
super().__init__(OperatorElement(divisor, suffix))


class StartOperator(BaseOperator):
def __init__(self):
pass

def __call__(self, number: int) -> OperatorArgs:
return OperatorArgsDTO(number, "").as_entity()


class StopOperator(BaseOperator):

def __init__(self):
pass

def __call__(self, a: OperatorArgs) -> OperatorArgs:
b = OperatorArgsDTO(a.number, a.result).as_entity()
if a.result == "":
b.add_suffix(str(a.number))
return b


def test_fizzbuzz():

def common_fizzbuzz(i: int) -> str:
if i % 15 == 0:
return "FizzBuzz"
elif i % 3 == 0:
return "Fizz"
elif i % 5 == 0:
return "Buzz"
else:
return str(i)

fizzbuzz: ComposableOperator = (
StartOperator()
+ DivisionOperator(3, "Fizz")
+ DivisionOperator(5, "Buzz")
+ StopOperator()
)
n = 100
for i in range(1, n + 1):
b = fizzbuzz(i)
assert b.result == common_fizzbuzz(b.number)


def test_ahobuzz():

def common_ahobuzz(i: int) -> str:
ans = ""
if "3" in str(i):
ans += "Aho"
if i % 5 == 0:
ans += "Buzz"
if ans == "":
ans = str(i)
return ans

ahobuzz: ComposableOperator = (
StartOperator()
+ ContainsOperator(3, "Aho")
+ DivisionOperator(5, "Buzz")
+ StopOperator()
)
n = 100
for i in range(1, n + 1):
b = ahobuzz(i)
assert b.result == common_ahobuzz(i)


if __name__ == "__main__":
test_fizzbuzz()
test_ahobuzz()
print("All tests passed")