paint-brush
Python で状態パターンを使用して無限に長いエッセイを書く@aayn
2,385 測定値
2,385 測定値

Python で状態パターンを使用して無限に長いエッセイを書く

Aayush10m2023/12/27
Read on Terminal Reader

長すぎる; 読むには

状態設計パターンは、オブジェクト指向コードの状態マシンです。このパターンを使用してエッセイ/文章ジェネレーターを作成します。
featured image - Python で状態パターンを使用して無限に長いエッセイを書く
Aayush HackerNoon profile picture
0-item

無限に長いエッセイを書いてみましょう!しかし、それは不可能な仕事です。ただし、永久に実行すると無限に長いエッセイが生成されるプロセスを作成することはできます。十分近い


これで、明らかに 1 行の Python コードで長くて繰り返しのエッセイを生成できます。


 >>> "This is water. " * 20 'This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. '


やあ…ブーブー!代わりに、この記事では State デザイン パターンを使用して、より興味深いエッセイを生成します。


まず、ステート マシンとは何か、またステート マシンがステート デザイン パターンにどのように関連しているかを理解します。次に、(それなりに) 興味深い無限のエッセイを生成できるステート マシンを作成します。次に、状態の設計パターンについて簡単に説明します。最後に、状態設計パターンを使用して、その状態マシンをオブジェクト指向コードに変換します。


ソフトウェア設計パターンは、一般的に発生する問題を解決する効果的な方法です。状態パターンなどのソフトウェア設計パターンを適切に適用すると、スケーラブルで保守性が高く、テストしやすいソフトウェアを作成するのに役立ちます。

ステートマシン

本質的に、ステート設計パターンはステート マシンをオブジェクト指向コードに変換します。


ステート マシンに詳しくない場合でも、これは非常に単純な概念です。ステート マシンには状態遷移があります。状態は対象となるシステムの特定のプロパティであり、状態遷移はこれらのプロパティを変更し、それによって状態変化も引き起こすアクションです。


私にはロボット工学のバックグラウンドがあり、ロボット工学ではステート マシンが広く使用されているため、ロボット掃除機の簡単な例を使用してステート マシンがどのように動作するかを説明します。


ロボット掃除機を駆動するステートマシン。


ステート マシンに遭遇したことがない場合でも、ステート マシンの図により、ロボットがどのように動作するかが直感的にわかります。この操作を段階的に見てみましょう。


  1. ロボットはドッキング状態で開始します (黒い点は開始状態を示します)。
  2. ロボットがバッテリーの残量が少ないことを検出すると、バッテリーがいっぱいになるまで充電を開始します (充電状態)。バッテリーがいっぱいになると、ドッキング状態に戻ります。
  3. ドッキング状態では、ロボットが床が汚れていることを検出すると (バッテリーが低下していない場合)、床の掃除を開始します (掃除状態)。
  4. 掃除状態では、ロボットのバッテリー残量が少なくなると、ロボットは自動的に充電を開始します。床がきれいであれば、ロボットはドック (ドック状態) に戻ります。


したがって、私たちのロボット掃除機には 3 つの状態 (ドッキング掃除中充電中) があり、床とバッテリーの感覚検出に基づいて遷移します。


無限エッセイのためのシンプルなステートマシン

ステート マシンを基本レベルで理解したので、無限のエッセイを作成できるステート マシンを作成しましょう。


無限エッセイを生成するステート マシン - バージョン 1


上記は、英語の文法を使用して、短くて単純な文で構成されるエッセイを生成するステート マシンです。すぐにもっと興味深いバージョンに到達することを約束しますが、これは理解のための良い出発点として役立つはずです。仕組みを見てみましょう。


  1. Noun状態から開始して、事前に定義された名詞のリストから選択して名詞を生成します。名詞が「The World」だとしましょう。 (ここまでの文章:「世界」)
  2. そして、最終的にVerb状態になり、次に動詞 (たとえば、「吠える」) を生成します。 (ここまでの文章:「世界は吠える」)
  3. Adjective状態で形容詞 (たとえば、「赤」) を生成します。 (ここまでの文章:「世界は赤く吠える」)
  4. 次に、 Endmarkステートで、終了句読点の 1 つ (たとえば「!」) を生成します。 (文:「世界は赤く吠える!」)
  5. 最後に、名詞状態に戻り、エッセイ内の次の文を生成します。


このステート マシンは、次のような (無意味な) エッセイを生成する可能性があります。


世界は赤く吠えます!いとこのハリーの雨はファウル?虎が楽しくきらめきます。 …


無限エッセイの非決定性ステートマシン

「非決定的」というと複雑に聞こえますが、私たちの目的では、ランダム性を追加することを意味します。基本的に、いくつかの状態に移行する前に、一種のコイントスを追加しています。私の言いたいことは分かるでしょう。

無限のエッセイを生成する非決定論的ステートマシン - バージョン 2


上記の非決定性ステート マシンは、前のステート マシンと非常によく似ています。唯一の違いは次のとおりです。

  • 否定は「いいえ」または「違う」のような単語であり、接続詞は「そして」や「しかし」のような単語です。
  • Verbステートでは、動詞を生成してからコインを投げます。表が出た場合 (50% の確率)、否定状態に移行します。それ以外の場合は、形容詞状態に進みます。
  • 同様に、 「形容詞」状態では、形容詞を生成してからコインを投げます。表の場合はコンジャンクション状態に進みます。尾の場合は、エンドマーク状態に進みます。


ランダム性、否定、接続詞の導入により、より興味深い可変長の文を生成できるようになりました。


状態設計パターン

ここで、状態設計パターンがどのように機能するかを理解しましょう。繰り返しになりますが、ステート マシンをオブジェクト指向コードに変換しようとしているということを思い出してください。


エッセイ生成ステート マシンでは、すべてのステートが 2 つのことを実行する必要があることに注目してください。

  1. 何らかのアクションを実行します。この場合、単語(名詞、形容詞など)を生成します。
  2. 次の状態に遷移します。名詞から動詞へ、など。


特定の州の観点から見ると、それ以外に知っておく必要があることや行う必要があることは何もありません。システム全体 (そのすべての状態と遷移) の複雑さに行き詰まってしまう代わりに、一度に 1 つの状態だけに集中することができます。私の考えでは、この種の分離切り離しが、State パターンの最大のセールスポイントです。


以下に、State 設計パターンのUML図を示します。 Contextクラスで示されるように、各状態が動作する何らかのコンテキストがあります。コンテキスト オブジェクトにはプライベート状態属性があり、現在の状態を呼び出してアクションを実行するために使用されます。各状態は、そのアクションまたは操作を実行し、次の状態を返すためのメソッドを備えたStateインターフェイスを実装します。


状態設計パターンの UML 図


これをエッセイ生成の例にマッピングすると、UML 図は次のようになります。


エッセイ生成用に実装された状態パターン


WordStateインターフェイスではなく抽象クラス (斜体で示されています) になりました。抽象クラスには、いくつかの抽象 (実装されていない) メソッドと属性を含めることができますが、その他のメソッドと属性を定義することもできます。インターフェイスは完全に抽象的です。インターフェイスのメソッドはすべて抽象的です。この変更を加えたのは、 generateWord実装がすべての州で同じであり、コードの重複を避けることが望ましいためです。


上記の各属性とメソッドを詳しく見てみましょう。 EssayContextクラスには次のものがあります。

  • state : 現在のWordStateオブジェクトへの参照。
  • essayBody : これまでに生成されたすべての単語のリスト。
  • setState() : state属性を変更するためのセッター。
  • addWord() : エッセイ本文に次の単語を追加するメソッド。
  • generateEssay() : このメソッドを呼び出してエッセイを生成します。 essayBodyの長さがlengthよりも大きい場合に停止します。
  • printEssay() : 生成されたエッセイの文字列を返します。


抽象クラスWordStateには次のものがあります。

  • wordList : 生成する単語を選択する単語のリストの抽象プロパティ (斜体で示されています)。
  • generateWord() : 生成された単語をエッセイコンテキストに追加するメソッド。
  • nextState() : 次の状態を返すための抽象メソッド。


WordStateから継承される他のすべての具体的な状態の代表的な例としてNounStateを使用します。

  • wordList : 生成する単語を選択する名詞のリスト。
  • nextState() : 次の状態を返します。


これで、これを実際にコードで実装するために必要なものがすべて揃いました。さあ、それではやってみましょう!


Pythonコード

まず、 essay_context.pyというファイルにEssayContextクラスを記述します。キャメルケースをやめてスネークケースに切り替えることにします。なぜなら、Python は...ヘビだからです (申し訳ありません)。


 from word_state import WordState class EssayContext: def __init__(self, state: WordState): self.state = state self.essay_body: list[str] = [] def set_state(self, state: WordState): self.state = state def add_word(self, word: str): self.essay_body.append(word) def generate_essay(self, length: int): while len(self.essay_body) < length: self.state.generate_word(self) def print_essay(self) -> str: return " ".join(self.essay_body)


次に、 word_state.pyというファイルに状態を追加しましょう。


 import abc import numpy as np class WordState(abc.ABC): word_list: list[str] @classmethod def generate_word(cls, context: "EssayContext"): word = np.random.choice(cls.word_list) context.add_word(word) context.set_state(cls.next_state()) @classmethod @abc.abstractmethod def next_state(cls) -> "WordState": pass class NounState(WordState): word_list: list[str] = ["everything", "nothing"] @classmethod def next_state(cls): return VerbState class VerbState(WordState): word_list: list[str] = ["is", "was", "will be"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return NegationState return AdjectiveState class NegationState(WordState): word_list: list[str] = ["not"] @classmethod def next_state(cls): return AdjectiveState class AdjectiveState(WordState): word_list: list[str] = ["fantastic", "terrible"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return ConjunctionState return EndmarkState class ConjunctionState(WordState): word_list: list[str] = ["and", "but"] @classmethod def next_state(cls): return NounState class EndmarkState(WordState): word_list: list[str] = [".", "!"] @classmethod def next_state(cls): return NounState


最後に、 main.py内のすべてを実行するコードを追加しましょう。


 from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay())


python main.pyを実行すると、次の出力が得られます (非決定性のため毎回異なります)。


 'everything is not terrible and nothing was terrible ! everything will be not fantastic but everything is fantastic . everything will be fantastic . nothing will be fantastic and nothing will be terrible ! everything was not fantastic and everything will be not terrible . everything was terrible . nothing was terrible but nothing will be fantastic ! nothing is not terrible . nothing was not fantastic but everything was not fantastic ! everything will be not fantastic but everything will be terrible ! everything will be not fantastic . everything is fantastic but nothing will be not terrible ! everything will be not fantastic but nothing was not fantastic !'


こんなシンプルなシステムも悪くないですね!また、さまざまな単語リストを拡張したり、状態を追加して、エッセイの生成をより洗練することもできます。エッセイを次のレベルに引き上げるために、いくつかの LLM API を導入することもできます。


最終的な考え

ステート マシンとステート パターンは、明確に定義された「状態」の概念を使用してシステムをモデル化および作成するのに最適です。つまり、各状態に関連付けられた特定の動作とプロパティが存在します。ロボット掃除機は掃除中、ドッキング中、または充電中です。テレビはオンまたはオフにすることができ、テレビのリモコン ボタンはテレビの状態に応じて異なる動作をします。


また、明確に定義されたパターンを持つシーケンスの生成または識別にも適しています。これは、エッセイ生成の例に当てはまります。


最後に、「これには一体何の意味があるの?」と疑問に思うかもしれません。この「無限」のエッセイを生成するために、さまざまな状態やクラスを定義するのに苦労したのはなぜでしょうか?同じ動作を実現するには、20 行 (またはそれ以下) の Python コードを作成することもできます。


簡単に言うと、スケーラビリティを向上させるためです。


たった 3 つまたは 5 つの州ではなく、50 または 500 の州があった場合を想像してください。これは誇張ではありません。実際のエンタープライズ システムには、そのレベルの複雑性があります。突然、State パターンは、その切り離しと分離により、はるかに魅力的に見えます。システム全体を頭の中に留めておく必要がなく、一度に 1 つの状態にだけ集中できます。ある状態が他の状態に影響を与えないため、変更を導入するのが簡単になります。また、スケーラブルで保守可能なシステムの重要な部分である単体テストも容易になります。


結局のところ、State パターンは単に状態を管理するだけではありません。すべてのデザイン パターンと同様に、これは洗練されていると同時に拡張性と保守性も備えたシステムを構築するための青写真です。