Help us understand the problem. What is going on with this article?

Python のリスト内包記法でズンドコしてみよう!

More than 1 year has passed since last update.

リスト内包記法でズンドコしたい!

最近お仕事で毎日 Python を書いているのですが、リスト内包記法に慣れてきたのでよく使ってます
ある日ふとズンドコキヨシのことを思い出し、リスト内包記法でズンドコを簡潔に書けたら嬉しいなと思いやってみました

もうだいぶ古くなってしまったネタですがよく知らない方はこちらをご参照くださいませ
ズンドコキヨシまとめ

やりたいこと

ズンドコキヨシの仕様を改めて確認すると、以下の動作を満たせばよいということです

  • ズン or ドコ をランダムに出力する
  • 直近5回の出力が 'ズン', 'ズン', 'ズン', 'ズン', 'ドコ' の場合に キ・ヨ・シ! を返して処理を終了する

これをリスト内包記法でいい感じにズンドコできないかなーと
イメージとしては以下のようなコードでズンドコが定義できたらなーと夢を持ちました

[x for x in zundoko()]

そして上記のコードが評価されるとこのようなリストが生成されて欲しいです

['ズン', 'ドコ', 'ズン', 'ズン', 'ズン', 'ズン', 'ズン', 'ドコ', 'キ・ヨ・シ!']

Level1. ジェネレーターでズンドコ

どうやらジェネレーターを上手く使えばできそうだということがわかったので、これまでジェネレーターについてあまり詳しく知らなかったのですが調べながらやってみました

まずできたのが以下のようなコードです

import random

def zundoko():
    history = []

    while True:
        history.append(random.choice(['ズン', 'ドコ']))
        yield history[-1]
        if history[-5:] == ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ']:
            yield 'キ・ヨ・シ!'
            break

print([x for x in zundoko()])

非常に基本的なジェネレーターです

Level2. ジェネレーター内で再帰する

Level1 のものだと while True が入るところがちょっと引っかかり再帰にしたいなーと思いました
そして考えたのが以下のコードです

import random

def zundoko(history=None):
    if history is None:
        history = []

    if history[-5:] == ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ']:
        yield 'キ・ヨ・シ!'
        return

    history.append(random.choice(['ズン', 'ドコ']))
    yield history[-1]
    yield from zundoko(history)

print([x for x in zundoko()])

Level3. 生成と判定を別にする

Level2 のものは値の生成とズンドコ判定が同じジェネレーターの中にあるので、そこを切り離したいなーと思いました
そして考えたのが以下のコードです

import random

def zundoko_gen():
    yield random.choice(['ズン', 'ドコ'])
    yield from zundoko_gen()

def zundoko():
    history = []
    for x in zundoko_gen():
        history.append(x)
        yield history[-1]
        if history[-5:] == ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ']:
            yield 'キ・ヨ・シ!'
            break

print([x for x in zundoko()])

Level4. 更に過去の window を生成する処理を別にする

(※更によい新たなパターンをコメントを頂き追記しました!)

Level3 では過去に生成されたズンドコを保持する処理も混ざってしまっているところが気持ち悪かったのですが、指定した個数分の window を生成するジェネレーターを間に挟むことで、より各々のジェネレータの役割が明確になりシンプルな構成にすることができました

import random

def zundoko_stream():
    """
    無限に 'ズン' or 'ドコ' を生成する
    """
    yield random.choice(['ズン', 'ドコ'])
    yield from zundoko_stream()


def zundoko_window(length):
    """
    生成されたズンドコの直近 length 個分の window を生成する
    """
    history = []
    for zundoko_val in zundoko_stream():
        history += [zundoko_val]
        history = history[-length:]
        yield history


def zundoko(pattern=['ズン'] * 4 + ['ドコ']):
    for window in zundoko_window(len(pattern)):
        yield window[-1]
        if window == pattern:
            yield 'キ・ヨ・シ!'
            break

print([x for x in zundoko()])

ズンドコを無限に生成するストリーム -> 任意の数だけ切り取る window -> window に対するパターンマッチ というより汎用的なズンドコストリーム処理になりましたね!

まとめ

リスト内包記法でズンドコというより、ジェネレーターでズンドコになってしまいました
ジェネレーターは奥が深いですね

なんかもっとスッキリできそうな感じがしますが、現状の僕の理解度だとこの程度です
使いこなすにはまだまだ修行が必要だと感じました

みんなもジェネレーターでリスト内包記法しよう!

scouty
「あらゆる事象を必然化し、 世の中のミスマッチをなくす」ことをミッションとし、AIヘッドハンティングサービスを開発・運営するスタートアップ
https://lapras.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away