LoginSignup
14
9

More than 1 year has passed since last update.

Python をデコってかわいくしよう!

Last updated at Posted at 2022-08-30

NTT データ数理システム の数理計画部でシニアリサーチャーをしている池田です.社内では Python 芸人をしています.

かわいさが足りない

まずは,とりあえず Fizz Buzz を Python で記述してみましょう.

for i in range(1, 101):
    if i % 15 == 0:
        print('FizzBuzz')
    elif i % 3 == 0:
        print('Fizz')
    elif i % 5 == 0:
        print('Buzz')
    else:
        print(i)

出力:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
:

何の面白味もありませんね.そして何よりかわいくない…🤔

もっとかわいく!

そこで,かわいく書き換えたものが以下です.

from functools import reduce, partial

def apply(f1):
    def _apply(f2):
        def wrapper(*args):
            f2a = f2(*args)
            return f1(*f2a) if isinstance(f2a, tuple) else f1(f2a)
        return wrapper
    return _apply

@apply(dict.fromkeys)
def make_n2func(n, func):  # {0: func, n, func, n*2: func, ..}
    return range(0,15,n), func

def calln2func(f):
    def wrapper(*args):
        n2func = f(*args)
        return lambda n: n2func[n%15](n)
    return wrapper

@calln2func  # @apply(lambda dct: lambda n: dct[n%15](n)) に同じ
@apply(partial(reduce, dict.__or__))  # Python 3.9 or more
@apply(map)
def make_fizzbuzz1(*args):
    return args

funcs = str, lambda n: 'Fizz', lambda n: 'Buzz', lambda n: 'FizzBuzz'
fizzbuzz1 = make_fizzbuzz1(make_n2func, (1, 3, 5, 15), funcs)

@apply(map)
@apply(lambda x: (fizzbuzz1, x))
@apply(range)
@apply(lambda x: (1, x+1))
def fizzbuzz(*args):
    return args
print(*fizzbuzz(100))

かわいい🥰!!!

Python には デコレータ という構文があり,関数(やクラス)をデコることができます.
今回はデコレータで関数をデコることで Fizz Buzz をかわいくしていきましょう.

デコレータの復習

まず,デコレータの復習をしておきましょう.

add1 = lambda x: x+1
mul2 = lambda x: x*2

def mul2deco(f):
    def wrapper(a):
        return mul2(f(a))
    return wrapper

def add1deco(f):
    def wrapper(a):
        return add1(f(a))
    return wrapper

@mul2deco
@add1deco
def spam(a):
    return a

spam(5)

は,

def spam(a):
    return a

mul2deco(add1deco(spam))(5)

と(ほぼ)等価です.下から順に適用されるので,この場合 (5+1)*2 = 12 となります.

また,デコレータは単なる関数なので,引数 → 加工する → 値を返す,といった程度のことしかできません.

方針

元のコードだとデコレータと相性が良くないので関数型っぽく書き換えます.

次のように 0 から 14 の数字と,それに適用する関数の辞書 n2func を用意し,1 から 100 まで map することにします.

fizz = lambda n: 'Fizz'
buzz = lambda n: 'Buzz'
fizzbuzz = lambda n: 'FizzBuzz'
n2func = {0: fizzbuzz, 1: str, 2: str, 3: fizz, 4: str, 5: buzz, 6: fizz, 7: str, 8: str, 9: fizz, 10: buzz, 11: str, 12: fizz, 13: str, 14: str}

print(*map(lambda n: n2func[n%15](n), range(1,101)))

出力:

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

n2func をべた書きで用意するのは品がないので,それぞれの倍数ごとに辞書を作成して merge しましょう.

n2func1 = {0: 'str', 1: 'str', 2: 'str', 3: 'str', 4: 'str', 5: 'str', 6: 'str', 7: 'str', 8: 'str', 9: 'str', 10: 'str', 11: 'str', 12: 'str', 13: 'str', 14: 'str'}
n2func3 = {0: 'fizz', 3: 'fizz', 6: 'fizz', 9: 'fizz', 12: 'fizz'}
n2func5 = {0: 'buzz', 5: 'buzz', 10: 'buzz'}
n2func15 = {0: 'fizzbuzz'}
print(n2func1 | n2func3 | n2func5 | n2func15)

出力:

{0: 'fizzbuzz', 1: 'str', 2: 'str', 3: 'fizz', 4: 'str', 5: 'buzz', 6: 'fizz', 7: 'str', 8: 'str', 9: 'fizz', 10: 'buzz', 11: 'str', 12: 'fizz', 13: 'str', 14: 'str'}

これらの辞書を作る関数 make_n2func を作ります.

def make_n2func(n, func):  # {0: func, n: func, n*2: func, ..}
    return dict.fromkeys(range(0,15,n), func)

print(make_n2func(1, 'str'))
print(make_n2func(3, 'fizz'))
print(make_n2func(5, 'buzz'))
print(make_n2func(15, 'fizzbuzz'))

出力:

{0: 'str', 1: 'str', 2: 'str', 3: 'str', 4: 'str', 5: 'str', 6: 'str', 7: 'str', 8: 'str', 9: 'str', 10: 'str', 11: 'str', 12: 'str', 13: 'str', 14: 'str'}
{0: 'fizz', 3: 'fizz', 6: 'fizz', 9: 'fizz', 12: 'fizz'}
{0: 'buzz', 5: 'buzz', 10: 'buzz'}
{0: 'fizzbuzz'}

map 関数を用いることで 4 つ一気に作ることができます.

print(*map(make_n2func, [1, 3, 5, 15], ['str', 'fizz', 'buzz', 'fizzbuzz']))

出力:

{0: 'str', 1: 'str', 2: 'str', 3: 'str', 4: 'str', 5: 'str', 6: 'str', 7: 'str', 8: 'str', 9: 'str', 10: 'str', 11: 'str', 12: 'str', 13: 'str', 14: 'str'} {0: 'fizz', 3: 'fizz', 6: 'fizz', 9: 'fizz', 12: 'fizz'} {0: 'buzz', 5: 'buzz', 10: 'buzz'} {0: 'fizzbuzz'}

merge は dict.__or__reduce することでできます.

PEP 584 – Add Union Operators To dict

from functools import reduce

print(reduce(dict.__or__, map(make_n2func, [1, 3, 5, 15], ['str', 'fizz', 'buzz', 'fizzbuzz'])))

出力:

{0: 'fizzbuzz', 1: 'str', 2: 'str', 3: 'fizz', 4: 'str', 5: 'buzz', 6: 'fizz', 7: 'str', 8: 'str', 9: 'fizz', 10: 'buzz', 11: 'str', 12: 'fizz', 13: 'str', 14: 'str'}

ここで一旦,補助関数を用意します.

add1 = lambda x: x+1
mul2 = lambda x: x*2

mul2(add1(5))  # 12

これを 5add1mul2 という流れが分かりやすくなるように,関数を順に合成する関数 compose を用意します.

def compose(*funcs):
    return reduce(lambda f, g: g(*f) if isinstance(f, tuple) else g(f), funcs)

compose(5, add1, mul2)  # 12

compose 関数を用いると,先ほどの n2func 作成は次のように表現できます.

from functools import partial

funcs = 'str', 'fizz', 'buzz', 'fizzbuzz'
n2func = compose((make_n2func, [1, 3, 5, 15], funcs),
                 map,
                 partial(reduce, dict.__or__)
                )

n2func は 0 から 14 までしか対応していないので数値に対して 15 で割った余りを渡す部分と,これを 1 から 100 まで map する部分を書けば OK です.

funcs = str, lambda n: 'Fizz', lambda n: 'Buzz', lambda n: 'FizzBuzz'
fizzbuzz1 = compose((make_n2func, [1, 3, 5, 15], funcs),
                    map,
                    partial(reduce, dict.__or__),
                    lambda dct: lambda n: dct[n%15](n),
                    )

print(*compose(100,
               lambda x: (1, x+1),
               range,
               lambda x: (fizzbuzz1, x),
               map,
              ))
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

あとはこれらをデコレータに書き直せば完成です.お疲れ様でした.
みんなも Python をかわいくしよう!

14
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
9