Лабораторная работа 13: конечные автоматы

Кувшинов Д.Р.

2017


Общее оглавление


Задача

Подробное описание конечных автоматов и примеры даны в 13-й самостоятельной работе.

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

Рассмотрим следующую задачу: дана последовательность чисел, требуется определить, состоит ли она из двух монотонных кусков (и если да, то какого вида) или нет. Также желательно найти границу между кусками. Всё это требуется сделать за один проход по последовательности.

Примеры

Последовательность “DOWN-DOWN”

{ 4, 3, 1, 6, 5, 2 }

[0] = 4  |****
[1] = 3  |***
[2] = 1  |*
[3] = 6  |******
[4] = 5  |*****
[5] = 2  |**


Последовательность “DOWN-UP”

{ 4, 2, 1, 0, 5, 7 }

[0] = 4  |****
[1] = 2  |**
[2] = 1  |*
[3] = 0  |
[4] = 5  |*****
[5] = 7  |*******


Последовательность “UP-DOWN”

{ 3, 5, 7, 4, 3, 2 }

[0] = 3  |***
[1] = 5  |*****
[2] = 7  |*******
[3] = 4  |****
[4] = 3  |***
[5] = 2  |**


Последовательность “UP-UP”

{ 3, 5, 7, 3, 6, 9 }

[0] = 3  |***
[1] = 5  |*****
[2] = 7  |*******
[3] = 3  |****
[4] = 6  |******
[5] = 9  |*********


Конечный автомат

Итак, у нас есть 5 исходов: DOWN-DOWN, DOWN-UP, UP-DOWN, UP-UP и “ошибка” (для последовательностей, не попадающих ни в одну из категорий). Попробуем составить конечный автомат, выполняющий классификацию. Ошибку приравняем к застопориванию автомата, поэтому у нас будет 4 принимающих состояния.

Четыре принимающих состояния
Четыре принимающих состояния

Очевидно, нас не интересуют абсолютные значения элементов последовательности. Нас интересует только происходит ли “возрастание” или “убывание”. Поэтому на вход автомату будем подавать результат сравнения пары соседних элементов последовательности. Т.е. в каждый момент времени нам требуется иметь доступ к двум соседним элементам. Например, { 1, 2, 3, 0, 1 } даёт { (2>1), (3>2), (0<3), (1>0) }. Для простоты записи будем выписывать только знак отношения, т.е. { >, >, <, > }.

Итак, если первый знак — <, то возможны только исходы DOWN-DOWN и DOWN-UP, а если первый знак — >, то — UP-DOWN и UP-UP.

Два возможных варианта
Два возможных варианта

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

Повторение
Повторение

Если же очередная пара состоит в противоположном отношении, то мы нашли точку между двумя кусками (здесь её можно запомнить).

Точка между кусками
Точка между кусками

От знака следующей пары зависит выбор окончательного варианта — типа последовательности.

Окончательная классификация
Окончательная классификация

Но автомат ещё не определён полностью. Во-первых, нужны стрелки из принимающих состояний (если последовательность ещё не кончилась). Во-вторых, нужно разобраться со случаем равенства. Всё это несложно сделать: нужно рассмотреть каждое состояние и определить, к какой ситуации относится тот или иной случай (переход по стрелке).

Окончательная классификация — исправлено
Окончательная классификация — исправлено

В принципе, список принимающих состояний можно расширить, включив в них монотонные последовательности. Получим автомат, являющийся расширением автомата из примера 3 в работе 13.

Финальный вариант
Финальный вариант


Код-заготовка

Пусть последовательность задана диапазоном (парой итераторов), произвольного типа. От элементов требуется только наличие операции <.

#include <iterator>
#include <utility> // pair
#include <vector>
#include <iostream>

enum Sequence_type
{
  seq_const     = 0,
  seq_down      = 1,
  seq_up        = 2,
  seq_down_down = 4,
  seq_down_up   = 5,
  seq_up_down   = 6,
  seq_up_up     = 7,
  seq_other     = 3
};

// Конечный автомат.
// Возвращает пару "тип последовательности", итератор начала второго куска (если подходящий тип последовательности).
// Пару можно создать с помощью функции make_pair.
template <class It>
std::pair<Sequence_type, It>
sequence_type(It from, It to)
{
  using std::make_pair;
  // Заполнить ...
}

// Тесты.
int test_sequence_type()
{
  using namespace std;
  // Тест на пустую.
  {
    int i;
    if (sequence_type(&i, &i).first != seq_const)
      return 1;
  }

  // Тест на константу -- 1 элемент.
  {
    int i = 0;
    if (sequence_type(&i, &i + 1).first != seq_const)
      return 2;
  }
  
  // Тест на константу -- 2 элемента.
  {
    int i[] { 1, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_const)
      return 3;
  }

  // Тест на константу -- 3 элемента.
  {
    int i[] { -1, -1, -1 };
    if (sequence_type(begin(i), end(i)).first != seq_const)
      return 4;
  }

  // Тест на монотонно возрастающую.
  {
    int i[] { -1, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_up)
      return 5;
  }
  
  // Тест на монотонно возрастающую.
  {
    int i[] { -1, 0, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_up)
      return 6;
  }
  
  // Тест на монотонно возрастающую.
  {
    int i[] { 0, 0, 1, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_up)
      return 7;
  }
  
  // Тест на монотонно убывающую.
  {
    int i[] { 1, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_down)
      return 8;
  }
  
  // Тест на монотонно убывающую.
  {
    int i[] { 1, 0, -1 };
    if (sequence_type(begin(i), end(i)).first != seq_down)
      return 9;
  }
  
  // Тест на монотонно убывающую.
  {
    int i[] { 1, 1, 0, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_down)
      return 10;
  }
  
  // Тест на случай down-down.
  {
    int i[] { 5, 3, 6, 4 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_down_down, begin(i) + 2))
      return 11;
  }
  
  // Тест на случай down-down.
  {
    int i[] { 5, 5, 3, 3, 6, 6, 4, 4 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_down_down, begin(i) + 4))
      return 12;
  }
  
  // Тест на случай down-up.
  {
    int i[] { 5, 3, 6, 8 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_down_up, begin(i) + 2))
      return 13;
  }
  
  // Тест на случай down-up.
  {
    int i[] { 5, 5, 3, 3, 6, 6, 8, 8 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_down_up, begin(i) + 4))
      return 14;
  }
  
  // Тест на случай up-down.
  {
    int i[] { 3, 5, 4, 0 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_up_down, begin(i) + 2))
      return 15;
  }
  
  // Тест на случай down-up.
  {
    int i[] { 3, 3, 5, 5, 4, 4, 0, 0 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_up_down, begin(i) + 4))
      return 16;
  }
  
  // Тест на случай up-up.
  {
    int i[] { 3, 5, 0, 4 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_up_up, begin(i) + 2))
      return 17;
  }
  
  // Тест на случай up-up.
  {
    int i[] { 3, 3, 5, 5, 0, 0, 4, 4 };
    if (sequence_type(begin(i), end(i)) != make_pair(seq_up_up, begin(i) + 4))
      return 18;
  }
  
  // Тесты на другие случаи.
  {
    int i[] { 1, 0, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 19;
  }
  {
    int i[] { 0, 0, 1, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 20;
  }
  {
    int i[] { 1, 2, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 21;
  }
  {
    int i[] { 0, 0, -1, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 22;
  }
  {
    int i[] { 0, 0, 1, 1, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 23;
  }
  {
    int i[] { 0, 0, 1, 1, 0, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 24;
  }
  {
    int i[] { 0, 0, 1, 1, 0, 0, 1 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 25;
  }
  {
    int i[] { 0, 2, 0, 2, 0, 2 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 26;
  }
  {
    int i[] { 0, 0, -1, -1, 0, 0 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 27;
  }
  {
    int i[] { 0, 0, -1, -1, 0, 0, -1 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 28;
  }
  {
    int i[] { 0, -2, 0, -2, 0, -2 };
    if (sequence_type(begin(i), end(i)).first != seq_other)
      return 29;
  }
  
  return 0; // Все тесты прошли успешно.
}

int main()
{
  std::cout << test_sequence_type() << std::endl;
  std::cin.ignore();
  return 0;
}

Общее оглавление

Кувшинов Д.Р. © 2017