LoginSignup
1
0

More than 5 years have passed since last update.

Pythonワンライナー:Fizzbuzzをイテレータ、クラスで

Posted at

Oneline

Pythonistaの嗜み。
セミコロンは甘え。

環境

  • Ubuntu 16.04
  • Python 3.5.2

ターゲット

イテレータクラスをワンライナーで定義して呼び出します。
例題としてFizzbuzzをやってみます。

  • イテレータクラスFizzBuzz
  • fb = FizzBuzz(n)とすると1番目からn番目までのFizzBuzzを順番に返すイテレータfbを得る
  • fbn + 1回以上__next__するとStopIterationを送出

必要になるテクニック

特に今回必要になるテクニックです。
以下を他の処理と接続できるように文ではなく関数またはオブジェクトで表現できるようにしなければなりません。

  • 変数の代入
  • import
  • クラス定義
  • 条件分岐
  • 複数の文を順番に実行するような処理(代入、if、return)など
  • 例外の送出

普通の実装

class FizzBuzz(object):
    def __init__(self, M):
        self.M = M
        self.i = -1
    def __iter__(self):
        return self
    def __next__(self):
        self.i += 1
        if self.i >= self.M:
            raise StopIteration()
        return "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)

fb = FizzBuzz(16)
print("\n".join(fb))

このように、__iter____next__(python2だとnext)を持つクラスのインスタンスはイテレータとなります。
そしてイテレータの終了条件に来ると StopIterationを送出すればイテレータの終わりと判断されます。
そしてjoinは文字列を返すイテレータを渡してやると、終了までの要素を指定された文字列でつなげてくれます。
イテレータの例:list

__next__関数のreturn文にあるformatの引数の中身が、self.i番目のFizzBuzzの処理となります。どこかのコードゴルフのものをパクり参考にしました。
joinに渡すために、"{}".format()で文字列化しました。str()でもいいのかもしれない。

カウンタself.iの初期値が-1なのが少々不格好ですが、ご容赦ください。

あと、15番目までやればFizzBuzzはとりあえず一周しますので、ターミナルの見やすさのためにfb = FizzBuzz(16)と16番目で終了させました。

クラス定義のみ関数化

FB = __import__("types").new_class(
    "FB",
    exec_body=lambda FB: FB.update({
        "__init__": lambda self, M: self.__dict__.update({"M": M, "i": -1}),
        "__iter__": lambda self: self,
        "__next__": (lambda self:
            self.__dict__.update({"i": self.i + 1}) or (
                iter([]).__next__() if self.i >= self.M else "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)
            )
        )
    })
)

fb = FB(16)
print("\n".join(fb))

クラス定義

typesモジュール中のnew_class関数を使います。import文も関数にして1行化しました。
python2ではnewモジュールが使えます。

参考:Pythonワンライナー:階乗の定義を3通りで

代入

"__init__"に渡されているlambdaの中身が、python onlinerにおける典型的な代入文になります。

複数文の実行

__next__関数では2つの文(インクリメント、return)を順番に実行しなければなりませんが、これをlambdaの中で書こうとすると一気に難しくなります。
NoneFalseとして扱われることを使うと、Noneを返す関数abcがある場合、

a() or b() or c()

とするとそれぞれが順番に実行されます。
update関数がNoneを返すのでこのテクニックが使え、"__next__"の次の行のような書き方となり、これがself.i += 1の操作に該当します。

例外

続いての困難はraise文です。今回は長さ0のイテレータの__next__を呼びだせば即StopIterationとなることを使いました。

条件

if文は普通に3項演算子的なあれです。
これに収めるために、Fizzbuzzの処理本体をコードゴルフのものにしました。

すべてを一行に

クラス定義のみ関数化したものを単純に改行を消して、print関数の中に押し込めただけです。

print("\n".join(__import__("types").new_class("FB", exec_body=lambda FB: FB.update({"__init__": lambda self, M: self.__dict__.update({"M": M, "i": -1}),"__iter__": lambda self: self, "__next__": (lambda self: self.__dict__.update({"i": self.i + 1}) or ( iter([]).__next__() if self.i >= self.M else "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)))}))(16)))

終わりに

「誰得」、この一言に尽きます。

1
0
2

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
1
0