Написание бесконечно длинного эссе с использованием шаблона состояния в Python

Написание бесконечно длинного эссе с использованием шаблона состояния в Python

28 декабря 2023 г.

Давайте напишем бесконечно длинное эссе! Но это невыполнимая задача. Однако мы можем создать процесс, который, если его запустить бесконечно, создаст бесконечно длинное эссе. Достаточно близко.

Теперь вы, очевидно, можете создать длинное и повторяющееся эссе с помощью одной строки кода 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 machine to drive a robot vacuum cleaner.

Диаграмма конечного автомата рисует интуитивно понятную картину того, как работает робот, даже если вы никогда не сталкивались с конечными автоматами. Давайте разберем эту операцию шаг за шагом.

  1. Робот запускается в состоянии Закреплен (черная точка указывает на начальное состояние).
  2. Если робот обнаруживает, что его батарея разряжена, он начинает заряжаться (состояние Зарядка), пока его батарея не заполнится. Когда аккумулятор полностью заряжен, он возвращается в состояние Закреплен.
  3. В состоянии Пристыкован, если робот обнаруживает, что пол грязный (и его батарея не разряжена), он начинает мыть пол (состояние Уборка).
  4. В состоянии Уборка, если у робота разряжается батарея, он начинает заряжаться самостоятельно. А если пол чистый, робот возвращается в док-станцию ​​(состояние Пристыкован).
  5. Таким образом, наш робот-пылесос имеет три состояния — Пристыкован, Уборка и Зарядка – и имеет переходы, основанные на сенсорном обнаружении пола и его аккумулятор.

    Простой конечный автомат для бесконечного эссе

    Теперь, когда мы понимаем конечные автоматы на базовом уровне, давайте создадим конечный автомат, способный написать бесконечное эссе.

    State machine to generate infinite essay - version 1

    Выше представлен конечный автомат, который использует английскую грамматику для создания эссе, состоящего из коротких простых предложений. Обещаю, очень скоро мы доберемся до более интересной версии, но это должно послужить хорошей отправной точкой для понимания. Давайте разберемся, как это работает.

    1. Начиная с состояния Существительное, мы генерируем существительное, выбирая его из заранее определенного списка существительных. Допустим, наше существительное — «Мир». (предложение на данный момент: «Мир»)
    2. Тогда мы попадаем в состояние Глагол, генерируя следующий глагол (скажем, «лает»). (предложение на данный момент: «Мир лает»)
    3. Мы генерируем прилагательное (скажем, «красный») в состоянии Прилагательное. (предложение на данный момент: «Мир лает красным»)
    4. Затем в состоянии Конечный знак мы генерируем один из завершающих знаков препинания, например «!». (предложение: «Мир лает красным!»)
    5. Наконец, мы вернулись в состояние Существительное, чтобы сгенерировать следующее предложение в эссе.
    6. Этот конечный автомат может сгенерировать (бессмысленное) эссе, похожее на это.

      <блок-цитата>

      Мир лает красным! Кузен Гарри фолит? Тигры весело мерцают. …

      Недетерминированный конечный автомат для бесконечного эссе

      Хотя слово «недетерминированный» звучит сложно, для наших целей это просто означает добавление некоторой случайности. По сути, мы добавляем своего рода подбрасывание монеты перед переходом в некоторые состояния. Вы поймете, что я имею в виду.

      Non-deterministic state machine to generate infinite essay - version 2

      Недетерминированный конечный автомат, описанный выше, очень похож на предыдущий. Единственные различия:

      • Отрицанием являются такие слова, как «нет» или «нет», а союзами — такие слова, как «и» и «но».
      • В состоянии Глагол мы генерируем глагол, а затем подбрасываем монету. Если выпадет решка (вероятность 50%), мы перейдем в состояние Отрицание; в противном случае мы переходим в состояние Прилагательное.
      • Аналогично, в состоянии Прилагательное мы генерируем прилагательное, а затем подбрасываем монету. Если выпадает решка, мы переходим в состояние Соединение; если это решка, то мы переходим в состояние Конечная метка.

      Благодаря введению случайности, отрицания и союзов мы теперь можем создавать более интересные предложения переменной длины.

      Шаблон проектирования состояний

      Теперь давайте разберемся, как работает шаблон проектирования состояний. Опять же, помните, что мы пытаемся преобразовать конечный автомат в объектно-ориентированный код.

      Обратите внимание, что в машине состояний для создания эссе каждое состояние должно сделать две вещи.

      1. Выполните какое-либо действие. В данном случае генерация слова (существительного, прилагательного и т. д.).
      2. Переход в следующее состояние. От Существительного до Глагола и т. д.
      3. С точки зрения конкретного состояния нет больше ничего, о чем ему нужно знать или что-то делать. Вместо того, чтобы увязнуть в сложности всей системы — всех ее состояний и переходов — мы можем просто сосредоточиться на одном состоянии за раз. На мой взгляд, такого рода изоляция и разделение является самым большим преимуществом шаблона State.

        Ниже представлена ​​UML диаграмма для шаблона проектирования «Состояние». Существует некоторый контекст, в котором работает каждое из состояний, иллюстрируемый классом Context. Объект контекста имеет атрибут частного состояния, который он использует для вызова текущего состояния для выполнения своего действия. Каждое состояние реализует интерфейс State с методами для выполнения его действия или операции и возврата следующего состояния.

        State design pattern UML diagram

        Если мы сопоставим это с примером создания эссе, диаграмма UML будет выглядеть следующим образом.

        State pattern implemented for essay generation

        :::информация WordState теперь является абстрактным классом (выделен курсивом), а не интерфейсом. Абстрактные классы могут иметь некоторые абстрактные (не реализованные) методы и атрибуты, а другие могут быть определены. Интерфейсы полностью абстрактны: все их методы абстрактны. Я внес это изменение, потому что реализация generateWord одинакова для всех состояний, и полезно избегать дублирования кода.

        :::

        Давайте разберем каждый из атрибутов и методов, описанных выше. В классе EssayContext у нас есть:

        • state: ссылка на текущий объект WordState.
        • essayBody: список всех слов, сгенерированных на данный момент.
        • setState(): установщик для изменения атрибута state.
        • addWord(): метод добавления следующего слова в текст эссе.
        • generateEssay(): мы вызываем этот метод для создания эссе; мы останавливаемся, когда длина essayBody превышает length.
        • printEssay(): возвращает строку сгенерированного эссе.

        В абстрактном классе WordState у нас есть:

        • wordList: абстрактное свойство (выделено курсивом) для списка слов, из которого мы выбираем слова для генерации.
        • generateWord(): метод, который добавляет сгенерированное слово в контекст эссе.
        • nextState(): абстрактный метод для возврата следующего состояния.

        Мы будем использовать NounState в качестве репрезентативного примера для всех других конкретных состояний, унаследованных от WordState.

        • wordList: список существительных, из которых мы выбираем слова для создания.
        • nextState(): возвращает следующее состояние.

        Теперь у нас есть все необходимое, чтобы реализовать это в коде. Давайте продолжим и сделаем именно это!

        Код Python

        Давайте сначала напишем класс EssayContext в файле с именем essay_context.py. Мы откажемся от верблюжьего случая и переключимся на змеиный, потому что 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 !'
        

        Неплохо для такой простой системы! Мы также можем расширить различные списки слов или добавить больше состояний, чтобы сделать создание эссе более сложным. Мы могли бы даже внедрить некоторые API-интерфейсы LLM, чтобы вывести наши эссе на новый уровень.

        Заключительные мысли

        Конечные автоматы и шаблон «Состояние» отлично подходят для моделирования и создания систем с четко определенным понятием «состояние». То есть с каждым состоянием связано определенное поведение и свойства. Робот-пылесос выполняет уборку, док-станцию ​​или зарядку. Ваш телевизор может быть включен или выключен, а кнопки пульта дистанционного управления телевизора будут действовать по-разному в зависимости от состояния телевизора.

        Он также хорошо подходит для генерации или идентификации последовательностей с четко определенным шаблоном. Это относится к нашему примеру создания эссе.

        Наконец, вы можете спросить: «Какой во всем этом смысл?» Почему мы потратили столько времени на определение различных состояний и классов, чтобы создать это «бесконечное» эссе? Мы могли бы написать 20 (или меньше) строк кода Python, чтобы добиться такого же поведения.

        Короткий ответ: для лучшей масштабируемости.

        Представьте себе, если бы вместо трех или пяти штатов у нас было бы 50 или 500 штатов. Это не гипербола; реальные корпоративные системы имеют такой уровень сложности. Внезапно модель «Государство» кажется гораздо более привлекательной из-за ее разъединенности и изоляции. Мы можем просто сосредоточиться на одном состоянии за раз, не держа в голове всю систему. Вносить изменения проще, поскольку одно состояние не повлияет на другие. Это также упрощает модульное тестирование, что является важной частью масштабируемой и удобной в обслуживании системы.

        В конечном счете, шаблон «Состояние» касается не только управления состояниями; Как и все шаблоны проектирования, это образец построения систем, которые будут настолько же масштабируемыми и удобными в обслуживании, насколько и сложными.


        Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE