{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "\n",
    "# Наивный (очень) алгоритм сравнения строк\n",
    "\n",
    "<pre>\n",
    "\n",
    "\n",
    "1  StrCmpNaive(text, pattern):\n",
    "2     lp = length(pattern)\n",
    "3     lt = length(text)\n",
    "4     matches = <b>new</b> List\n",
    "5     <b>for</b> i <b>in</b> range 0..lt <b>exclusively</b>  // что здесь не так?\n",
    "6         <b>for</b> j <b>in</b> range 0..lp <b>exclusively</b>   \n",
    "7             <b>if</b> text[i+j] != pattern[j]\n",
    "8                 <b>break</b>\n",
    "9             <b>if</b> j == lp - 1\n",
    "10                matches.append(i)\n",
    "11    return matches\n",
    "\n",
    "\n",
    "</pre>\n",
    "> Ассимптотическая сложность в наихудшем случае = $O(?)$\n",
    "\n",
    "> Какой паттерн (и для какого текста) будет приводить к максимально неэффективной работе алгоритма? (* в случае, если совпавшие строки _не перекрываются_)\n",
    "    \n",
    "> Например, поиск `'aaaaaaab'` в `'aaaaaaaaaaaaaaaaaaaaaa'` и тому подобное. Или поиск паттернов из повторяющихся символов, если допускается частичное пересечение разных выравниваний. К примеру, поиск `'aaaa'` в `'aaaaaaaaaaaaaaaaa'`\n",
    "    \n",
    "# Мы можем лучше\n",
    "\n",
    "Мы будем предполагать следующее:\n",
    "* Паттерн (в большинстве случаев) значительно меньше, чем сам текст\n",
    "* Алфавит ограничен, и его мы тоже знаем, естественно. Алфавит - множество символов, из которых состоят текст и паттерн\n",
    "\n",
    "Одна из (относительно) простых идей, которую можно использовать для ускорения поиска:\n",
    "> Если встреченный в тексте <i>не совпадающий</i> с паттерном символ <i>вообще</i> не встречается в паттерне, можно смело \"проматывать\" начало паттерна и располагать его сразу за этим символом\n",
    "\n",
    "<pre>\n",
    "1  StrCmpLessNaive(text, pattern):\n",
    "2     lp = Length(pattern)\n",
    "3     lt = Length(text)\n",
    "4     matches = <b>new</b> List\n",
    "5     preprocessed = Preprocess(pattern)\n",
    "6     \n",
    "7     shift = 0\n",
    "8     <b>while</b> shift < lt - lp + 1:\n",
    "9         j = 0\n",
    "10        <b>while</b> j < lp <b>and</b> text[shift+j] == pattern[j]:\n",
    "11            j += 1\n",
    "12        <b>if</b> j == lp:\n",
    "13            matches.append(shift)\n",
    "14            shift += 1\n",
    "15        <b>else</b>:\n",
    "16            shift += UsePreprocessed(text[shift+j], shift, preprocessed)\n",
    "17     <b>return</b> matches\n",
    "</pre>\n",
    "\n",
    "<b>Возникают следующие вопросы:</b>\n",
    "* Как выглядит применение эвристики \"перемотки\"? \n",
    "* Как улучшить эвристику?\n",
    "\n",
    "\n",
    "\n",
    "<forn size=10>\n",
    "    \n",
    "<b>   \n",
    "`shift = 0`\n",
    "<pre><font color=\"red\">H</font>ERE IS A SIMPLE EXAMPLE   \n",
    "<font color=\"red\">E</font>XAMPLE\n",
    "^\n",
    "</pre>\n",
    "    \n",
    "`j = 0; shift = shift + j + 1`  \n",
    "`shift = 1`\n",
    "\n",
    "<pre>H<font color=\"green\">E</font><font color=\"red\">R</font>E IS A SIMPLE EXAMPLE \n",
    " <font color=\"green\">E</font><font color=\"red\">X</font>AMPLE\n",
    "  ^\n",
    "</pre> \n",
    "`j = 1; shift = shift + j + 1`  \n",
    "`shift = 3`\n",
    "\n",
    "<pre>HER<font color=\"green\">E</font><font color=\"red\"> </font>IS A SIMPLE EXAMPLE \n",
    "   <font color=\"green\">E</font><font color=\"red\">X</font>AMPLE\n",
    "    ^\n",
    "</pre>\n",
    "`j = 1; shift = shift + j + 1`  \n",
    "`shift = 5`\n",
    "\n",
    "</b>\n",
    "<font>\n",
    "\n",
    "> И так далее. Пока решение выглядит очень сырым. Как можно _существенно_ улучшить его?\n",
    "\n",
    "\n",
    "Чтобы ускорить обработку последовательностей еще сильнее, нам придется взглянуть на проблему с другой стороны - буквально. \n",
    "\n",
    "Правда, для этого придется немного переписать основной алгоритм.\n",
    "\n",
    "## Сравнение строк с (правого) конца паттерна\n",
    "\n",
    "<pre>\n",
    "1  StrCmpReversed(text, pattern):\n",
    "2     lp = Length(pattern)\n",
    "3     lt = Length(text)\n",
    "4     matches = <b>new</b> List\n",
    "5     preprocessed = Preprocess(pattern)\n",
    "6     \n",
    "7     shift = 0\n",
    "8     <b>while</b> shift < lt - lp + 1:\n",
    "9         j = lp - 1\n",
    "10        <b>while</b> j >= 0 <b>and</b> text[shift+j] == pattern[j]:\n",
    "11            j -= 1\n",
    "12        <b>if</b> j <= 0:\n",
    "13            matches.append(shift)\n",
    "14            shift += 1\n",
    "15        <b>else</b>:\n",
    "16            <b>if</b> text[j+shift] not in preprocessed == 0:\n",
    "17                shift += (j + 1)\n",
    "18            <b>else</b>:\n",
    "19                shift += 1\n",
    "20     <b>return</b> matches\n",
    "</pre>\n",
    "\n",
    "\n",
    "<forn size=10>\n",
    "<b> \n",
    "    \n",
    "`shift = 0`\n",
    "<pre>HERE I<font color=\"red\">S</font> A SIMPLE EXAMPLE   \n",
    "EXAMPL<font color=\"red\">E</font>\n",
    "      ^\n",
    "</pre>\n",
    "    \n",
    "`j = 6; shift = shift + j + 1`  \n",
    "`shift = 7`\n",
    "\n",
    "<pre>HERE IS A SIM<font color=\"red\">P</font>LE EXAMPLE   \n",
    "       EXAMPL<font color=\"red\">E</font>\n",
    "             ^\n",
    "</pre> \n",
    "`shift = shift + 1`  \n",
    "`shift = 8`\n",
    "\n",
    "<pre>HERE IS A SIMP<font color=\"red\">L</font>E EXAMPLE   \n",
    "        EXAMPL<font color=\"red\">E</font>\n",
    "              ^\n",
    "</pre> \n",
    "`shift = shift + 1`  \n",
    "`shift = 9`\n",
    "\n",
    "<pre>HERE IS A S<font color=\"red\">I</font><font color=\"green\">MPLE</font> EXAMPLE   \n",
    "         EX<font color=\"red\">A</font><font color=\"green\">MPLE</font>\n",
    "           ^\n",
    "</pre> \n",
    "`j = 2; shift = shift + j + 1`  \n",
    "`shift = 12`\n",
    "\n",
    "<pre>HERE IS A SIMPLE E<font color=\"red\">X</font>AMPLE   \n",
    "            EXAMPL<font color=\"red\">E</font><font color=\"green\"></font>\n",
    "                  ^\n",
    "</pre>\n",
    "\n",
    "`shift = shift + 1`  \n",
    "`shift = 13`\n",
    "\n",
    "<pre>HERE IS A SIMPLE EX<font color=\"red\">A</font>MPLE   \n",
    "             EXAMPL<font color=\"red\">E</font><font color=\"green\"></font>\n",
    "                   ^\n",
    "</pre>\n",
    "\n",
    "`shift = ?`  \n",
    "Можно ли улучшить эвристику \"сдвиг на 1\" в данном случае?\n",
    "\n",
    "</b>\n",
    "<font>\n",
    "\n",
    "В случае, если символа, встреченного в тексте, нет в паттерне, весь паттерн сдвигается правее этого символа.\n",
    "\n",
    "А если есть? Предположим, что мы искали бы не весь паттерн, а только его часть - т.е. брали бы _суффикс_ паттерна до встречи с символом `A`: \n",
    "\n",
    "> <pre><b>MPLE</b></pre>\n",
    "\n",
    "тода решение было бы простым - сдвинуть паттерн правее символа. Иначе говоря, сдвигать его до тех пора, пока можно с уверенностью сказать, что \"плохого\" символа в паттерне нет.\n",
    "\n",
    "Теперь вспомним, что у паттерна есть вторая часть (то есть просто его начало):\n",
    "\n",
    "> <pre><b><font color=\"blue\">EXA</font>MPLE</b></pre>\n",
    "\n",
    "До этой части можно спокойно проматывать. Если мы сделаем так, то окажется, что эвристика сдвигает последний (самый правый) символ `A`  паттерна как раз до совпадения с таким же символом в тексте.\n",
    "\n",
    "<b>\n",
    "<pre>HERE IS A SIMPLE EX<font color=\"red\">A</font>MPLE   \n",
    "                 EX<font color=\"red\">A</font>MPLE<font color=\"green\"></font>\n",
    "                       ^\n",
    "</pre>\n",
    "</b>\n",
    "\n",
    "> (нам повезло, и поиск закончился, но это не всегда так)\n",
    "\n",
    "Как расчитать сдвиг? Ведь \"плохой\" символ может встретиться не только в конце паттерна, но и во время проверки \"в середине\" паттерна. Препроцессинг должен покрывать все эти случаи. \n",
    "\n",
    "\n",
    "# Подготовка символов\n",
    "\n",
    "Нам потребуется функция `AlphabetIndex(char)`, которая переводит символ в целое чисо: $f: Char \\rightarrow Int$. При помощи этой функции будем получать фиксированные числовые индексы для символов из алфавита.\n",
    "\n",
    "\n",
    "Вторая функция, более важная - это функция, которая подготовит таблицу для работы эвристики \"плохого символа\".\n",
    "\n",
    "\n",
    "<pre>\n",
    "1  BadCharacterTable(pattern):\n",
    "2      <b>if</b> Length(pattern) == 0: \n",
    "3          <b>return new</b> Array[Int] of shape (0, |Alphabet|)\n",
    "4      support = <b>new</b> Array[Int] of -1 of length |Alphabet|\n",
    "5      table = <b>new</b> Array[Int] of shape (1 + Length(pattern), |Alphabet|)\n",
    "6      <b>for</b> i <b>in</b> 0..Length(pattern) exclusively:\n",
    "7          support[AlphabetIndex(pattern[i])] = i\n",
    "8          <b>for</b> j <b>in</b> 0..Length(pattern) exclusively:\n",
    "9              table[j][j+1] = support[j]\n",
    "10     <b>return</b> table\n",
    "</pre>\n",
    "\n",
    "Есть и более простой подход, который создает одномерную таблицу, в которой записаны для каждого символа алфавита расстояние от последнего символа до _самого правого_ вхождения в строку. Если символа вообще нет, записывается длина строки: \n",
    "\n",
    "<pre>\n",
    "1 BadCharacterVariant(pattern):\n",
    "2     lp = Lentgh(pattern);\n",
    "3     table = <b>new</b> Table of size |Alphabet|\n",
    "4     <b>for<b> i <b>in</b> 0..|Alphabet| exclusively:\n",
    "5         table[i] = lp;\n",
    "6     <b>for<b> i <b>in</b> 0..(|lp| - 1) exclusively:\n",
    "7         table[pattern[i]] = lp - 1 - i\n",
    "8     <b>return</b> table\n",
    "</pre>\n",
    "\n",
    "\\- но она несовместима с дальнейшей версией кода (из-за разных вовзращаемых значений)\n",
    "\n",
    "\n",
    "## Пример работы функции с небольшим алфавитом\n",
    "\n",
    "Алфавит: $\\{A, C, G, T\\}$\n",
    "\n",
    "Паттерн: $GATTACA$\n",
    "\n",
    "Таблица:\n",
    "\n",
    "<pre><b>\n",
    "        G   A   T   T   A   C   A\n",
    "A| -1, -1,  1,  1,  1,  4,  4,  6\n",
    "C| -1, -1, -1, -1, -1, -1,  5,  5\n",
    "G| -1,  0,  0,  0,  0,  0,  0,  0\n",
    "T| -1, -1, -1,  2,  3,  3,  3,  3\n",
    "</b></pre>\n",
    "\n",
    "\n",
    "Применяя подобную таблицу, можно гарантированно перемотать до \"самого правого\" относительно сравниваемого в данный момент \"плохого\" символа. Этот алгоритм является упрощением алгоритма Бойера-Мура и называется **алгоритмом Бойера-Мура-Хорспула**.\n",
    "\n",
    "Чуть позже сравним его с \"полной\" версией.\n",
    "\n",
    "<pre>\n",
    "1  BoyerMooreHorspool(text, pattern):\n",
    "2     lp = Length(pattern)\n",
    "3     lt = Length(text)\n",
    "4     matches = <b>new</b> List\n",
    "5     preprocessed = BadCharacterTable(pattern)\n",
    "6     \n",
    "7     shift = 0\n",
    "8     <b>while</b> shift < lt - lp + 1:\n",
    "9         j = lp - 1\n",
    "10        <b>while</b> j >= 0 <b>and</b> text[shift+j] == pattern[j]:\n",
    "11            j -= 1\n",
    "12        <b>if</b> j <= 0:\n",
    "13            matches.append(shift)\n",
    "14            shift += 1\n",
    "15        <b>else</b>:\n",
    "16            shift += (j - prepocessed[alphabet_index(text[shift+j])][j])\n",
    "17            // Альтернативный вариант с \"одномерной\" таблицей\n",
    "18            // shift += prepocessed[alphabet_index(text[shift+j])]) - j\n",
    "19     <b>return</b> matches\n",
    "</pre>\n",
    "\n",
    "\n",
    "# Эвристика \"хорошего суффикса\"\n",
    "\n",
    "То, что мы только что рассмотрели выше, называется _\"эвристикой плохого символа\"_.\n",
    "\n",
    "**Алгоритм Бойера-Мура** использует дополнительную эвристику, называемую эвристикой _\"хорошего суффикса\"_. При каждом несовпадении этот алгоритм вычисляет _обе_ эвристики, и затем выбирает сдвиг по наилучшей (то есть мсаксимальный сдвиг).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "\n",
    "\n",
    "## Описание эвристики \"хорошего суффикса\" с примерами\n",
    "\n",
    "Для начала - как эвристика вообще работает.\n",
    "\n",
    "Пусть для данного выравнивания паттерна <b>P</b> и текста <b>T</b> подстрока <b>t</b> текста <b>T</b> совпадает с суффиксом <b>P</b>, но левее <b>t</b> происходит несовпадение. \n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "     GAAGC<font color=\"green\">AAG</font>\n",
    "     \n",
    "t = AAG\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "Тогда, если существует, найдем самое правое вхождение <b>t'</b> == <b>t</b> в паттерн <b>P</b>, такое, что символ левее <b>P</b> отличается от символа левее <b>t</b> в паттерне <b>P</b>.\n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "     <font color=\"blue\">G</font><font color=\"green\">AAG</font><font color=\"blue\">C</font><font color=\"green\">AAG</font>\n",
    "     \n",
    "t = AAG после C\n",
    "t' = AAG после G\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "Сдвинем паттерн <b>P</b> вправо так, чтобы <b>t'</b> из паттерна совпадало с <b>t</b> из текста.\n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "         G<font color=\"green\">AAG</font>CAAG\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "Если же <b>t'</b> в паттерне <b>P</b> не существует, до сдвинем левый край <b>P</b> левее <b>t</b> из текста <b>T</b> как можно _меньше_, чтобы _префикс_ <b>P</b> совпадал с суффиксом <b>t</b>, если такой префикс существует.\n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "паттерн изменен на AGAGCAAG с целью демонстрации\n",
    "\n",
    "SAGGTAGACT<font color=\"green\">A</font><font color=\"blue\">AG</font>ACTTACCCAT\n",
    "          |<font color=\"blue\">AG</font>AGCAAG\n",
    "          |||\n",
    "          |||\n",
    "          <font color=\"green\">AAG</font>AGCAAG\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "Если префикса нет, сдвинем паттерн левее подстроки <b>t</b> из текста.\n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "             TCAGCAAG\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "### На примере фразы\n",
    "Как работает эвристика \"хорошего суффикса\". Синим обозначен суффикс, размещенный в начале паттерна, в том числе его \"воображаемая\" часть.\n",
    "\n",
    "<pre>HERE IS A S<font color=\"red\">I</font><font color=\"green\">MPLE</font> EXAMPLE -> HERE IS A SI<font color=\"green\">MPLE</font> EXAMPLE   \n",
    "      <font color=\"blue\">MPLE</font>X<font color=\"red\">A</font><font color=\"green\">MPLE</font>         ->             <font color=\"blue\">MPLE</font>XAMPLE\n",
    "           ^\n",
    "</pre>           \n",
    "\n",
    "Для сравнения, \"плохой символ\" в той же ситуации дает меньший сдвиг:\n",
    "\n",
    "<pre>HERE IS A S<font color=\"red\">I</font><font color=\"green\">MPLE</font> EXAMPLE -> HERE IS A S<font color=\"blue\">I</font>MPLE EXAMPLE\n",
    "        <font color=\"blue\">I</font>EX<font color=\"red\">A</font><font color=\"green\">MPLE</font>         ->            <font color=\"blue\">I</font>EXAMPLE\n",
    "</pre>\n",
    "\n",
    "\n",
    "> В алгоритме Бойера-Мура после вычисления выбирается лучшая эвристика \n",
    "\n",
    "\n",
    "    \n",
    "## Расчет эвристики \"хорошего суффикса\"\n",
    "\n",
    "Для того, чтобы воспользвоаться перечисленными выше правилами, нам нужно составить таблицы сдвигов для того, чтобы усрорить процесс сдвига. Таблицы составляются за счет предобработки _только_ паттерна. \n",
    "\n",
    "Расчет таблиц для эвристики - сложный процесс, поэтому мы разберем его по частям, \"снизу вверх\".  \n",
    "Первая функция, которая потребуется, ищет повторения строки в самой себе по заданным индексам, и возвращает длину совпавшей подстроки:\n",
    "\n",
    "\n",
    "<pre>\n",
    "1  MatchLength(string, i, j):\n",
    "2      <b>if</b> i == j:\n",
    "3          <b>return</b> Length(string) - i\n",
    "4       match_length = 0 \n",
    "5       ls = Length(s)\n",
    "6       <b>while</b> i < ls <b>and</b> j < ls <b>and</b> string[i] == string[j]\n",
    "7            match_length += 1\n",
    "8            i += 1\n",
    "9            j += 1\n",
    "10      <b>return</b> match_length\n",
    "</pre>\n",
    "\n",
    "Если вернуться к примеру выше, эта функция дала бы примерно такие результаты:\n",
    "\n",
    "<pre>\n",
    "MatchLength(<b>GAAGCAAG</b>, 1, 5) == 3\n",
    "</pre>\n",
    "         \n",
    "Т.е. следующие три символа после 1-го и 5-го включительно совпадают:\n",
    "\n",
    "<pre><b>G<font color=\"green\">AAG</font>C<font color=\"green\">AAG</font></b></pre>\n",
    "\n",
    "\n",
    "## Какие таблицы понадобятся для работы алгоритма\n",
    "\n",
    "Для работы алгоритма используются две одномерные таблицы: `L` и `F`. Таблица `F` используется только в случае, если `L[i]` содержит \"пустое\" значение, или найдено совпадение паттерна и подстроки.\n",
    "\n",
    "В таблице L содержится следующая информация:\n",
    "\n",
    "Для всех `i`, `L[i]` содержит наибольшую (самую правую) позицию меньше `n`, такую, что строка `P[i..n]` совпадает с суффиксом `P[1..L[i]]`, и символ, предшествующий этому суффиксу, не равен `P[i-1]` (то есть символу до `P`). Если такой позиции нет, `L[i]` равен 0.\n",
    "\n",
    "\n",
    "Таблица `F` описывает величину наибольшего суффикса `P[i..n]` для `F[i]`, который также является префиксом `P`. если такого суффикса нет, `P[i] = 0`\n",
    "\n",
    "> Пока становится только запутанее. Давайте посмотрим на примеры\n",
    "\n",
    "\n",
    "### Предобработка\n",
    "\n",
    "Функция FindPrefixes(string) возвращает массив `Z` такой, что `Z[i]` содержит число, равное длине подстроки `S[i]`, котороая при этом является и префиксом `S`. Если такого префикса нет, то `Z[i]` будет 0.\n",
    "\n",
    "Пример:\n",
    "<pre>\n",
    "FindPrefixes(\"coconut\")\n",
    "--\n",
    "[7, 0, 2, 0, 0, 0, 0]\n",
    "\n",
    "FindPrefixes(\"aaaaaaa\")\n",
    "--\n",
    "[7, 6, 5, 4, 3, 2, 1]\n",
    "</pre>\n",
    "\n",
    "> Домашнее задание - реализовать данную функцию.\n",
    "\n",
    "Как вы уже могли преположить, данная функция используется для создания таблиц `L` и `H`. \n",
    "\n",
    "Однако, поскольку мы ведем сравнение \"с конца\" паттерна, то и функцию будем применять к развернутой строке (или сама функция пишется так, чтобы искать суффиксы - в принципе, разницы нет)\n",
    "\n",
    "<pre>\n",
    "\n",
    "FindPrefixes(Reversed(\"EXAMPLE\"))\n",
    "--\n",
    "[7, 0, 0, 0, 0, 0, 1]\n",
    "\n",
    "FindPrefixes(\"ELMAXE\")\n",
    "--\n",
    "[7, 0, 0, 0, 0, 0, 1]\n",
    "\n",
    "\n",
    "</pre>\n",
    "\n",
    "### Создание таблицы сдвигов L\n",
    "\n",
    "По умолчанию используется эта таблица. Что она содержит?\n",
    "\n",
    "При помощи этой таблицы можно вычислить сдвиг, если не совпал `i-1` элемент, по следующей формуле:\n",
    "\n",
    "> `shift = Length(pattern) - L[i] - 1`\n",
    "\n",
    "\n",
    "<pre><b>\n",
    "GoodSuffixTable(\"EXAMPLE\")\n",
    "--\n",
    "  E   X   A   M   P   L   E\n",
    "[-1, -1, -1, -1, -1, -1,  0]\n",
    "-- Сдвиг\n",
    "[ -,  -,  -,  -,  -,  -,  -,  7]\n",
    "</b></pre>\n",
    "\n",
    "\\- здесь единственный совпадающий суффикс - это последняя `'E'`\n",
    "\n",
    "<pre><b>\n",
    "GoodSuffixTable(\"GAAGCAAG\")\n",
    "--\n",
    "  G   A   A   G   C   A   A   G\n",
    "[-1, -1, -1, -1, -1,  3, -1,  0]\n",
    "-- Сдвиг\n",
    "[ -,  -,  -,  -,  -,  4,  -,  7]\n",
    "</b></pre>\n",
    "\n",
    "`\"G\"` в центре паттерна игнорируется, так как сдвигаться до суффикса `'G'` перед которым идет `'A'`, когда алгоритм \"споткнулся\" об `\"AG\"`, бессмысленно.  Получается, что \"сдвиг\" нужен до первой `'G'`.\n",
    "\n",
    "<pre><b>\n",
    "GoodSuffixTable(\"GATGCAAG\")\n",
    "--\n",
    "  G   A   T   G   C   A   A   G\n",
    "[-1, -1, -1, -1, -1, -1, -1,  3]\n",
    "-- Сдвиг\n",
    "[ -,  -,  -,  -,  -,  -,  -,  4]\n",
    "</b></pre>\n",
    "\n",
    "А теперь все нормально, потому что в центре перед `'G'` расположено `'T'`, а не `'A'`.\n",
    "\n",
    "\n",
    "Таблица используется для того, чтобы правильно свидгать паттерн относительно текста так, чтобы на совпавший в прошлый раз суффикс накладывалась самая правая часть, содержащая ту же подстроку:\n",
    "\n",
    "\n",
    "<pre>\n",
    "<b>\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "     <font color=\"blue\">G</font><font color=\"green\">AAG</font><font color=\"blue\">C</font><font color=\"green\">AAG</font>\n",
    "\n",
    "После сдвига:\n",
    "\n",
    "SAGGTAGACT<font color=\"green\">AAG</font>ACTTACCCAT\n",
    "         G<font color=\"green\">AAG</font>CAAG\n",
    "</b>\n",
    "</pre>\n",
    "\n",
    "Совпадение строки с самой собой не важно, поэтому `L[0] = -1`. Если `L[i]` сожержит `-1` (подходящего суффикса нет), осуществляется переход к следуюущей таблице, \"таблице полного сдивга\" `H` (\"full shift table\"). В случае, если произошло несовпадение символов текста и паттерна на `i-1`-м символе, выполняется сдвиг на `Length(pattern) - L[i]`.\n",
    "\n",
    "`L[i] = k`, где `S[i:]` совпадает с `S[:k]`. Несовпадающие суффиксы равны `-1`. Например, для `EXAMPLE` будет:  \n",
    "\n",
    "\n",
    "**Псевдокод алгоритма**:\n",
    "<pre>\n",
    "GoodSuffixTable(string):\n",
    "    L = <b>new</b> Array[Int] of -1 of size Length(string)\n",
    "    Z = FindPrefixes(Reversed(S)) \n",
    "    Z = Reverse(Z)\n",
    "    <b>for</b> j <b>in</b> 0..Length(S)-1 exclusively:\n",
    "        i = Length(S) - Z[j]\n",
    "        if i != Length(S):\n",
    "            L[i] = j\n",
    "    return L\n",
    "    \n",
    "</pre>\n",
    "\n",
    "\n",
    "### Создание таблицы полного сдвига H\n",
    "\n",
    "Это таблица частично совпадающих префиксов. Переходим к ней в ситуации, если совпадающего суффикса не существует.\n",
    "\n",
    "Используется схожим с таблицей `L` образом: если `i-1`-й элемент паттерна не совпал, то паттерн перемещается на `Length(P) - F[i]` вправо.\n",
    "\n",
    "По сути, это таблица, содержащую размеры следующей вещи: самого длинного суффикса строки `S[i:]` (можно сказать, что это _суффикс суффикса_ паттерна), который одновременно является и префиксом S.\n",
    "\n",
    "На примере `\"EXAMPLE\"`:\n",
    "\n",
    "\n",
    "\n",
    "<pre><b>\n",
    "FullShiftTable(\"BONOBO\")\n",
    "--\n",
    "               'O'\n",
    "            'B  O'\n",
    "         'O  B  O'\n",
    "      'N  O  B  O'\n",
    "   'O  N  O  B  O'\n",
    "'N  O  N  O  B  O'\n",
    "--\n",
    "[6, 2, 2, 2, 2, 0]\n",
    "-- Сдвиг\n",
    "[0, 4, 4, 4, 4, 6]\n",
    "</b></pre>\n",
    "\n",
    "\\- Другой пример:\n",
    "\n",
    "\n",
    "<pre><b>\n",
    "FullShiftTable(\"EXAMPLE\")\n",
    "--\n",
    "[7, 1, 1, 1, 1, 1, 1]\n",
    "-- Сдвиг\n",
    "[0, 6, 6, 6, 6, 6, 6]\n",
    "</b></pre>\n",
    "\n",
    "\n",
    "**Псевдокод функции**\n",
    "\n",
    "<pre>\n",
    "FullShiftTable(string):\n",
    "    F = <b>new</b> Array[Int] of 0 of size Length(string)\n",
    "    Z = FindPrefixes(string)\n",
    "    Z = Reversed(Z)\n",
    "    longest = 0\n",
    "    <b>for</b> i <b>in</b> Length(Z):\n",
    "        <b>if</b> Z[i] == i+1:\n",
    "            longest = max(Z[i], longest)\n",
    "        <b>else</b>:\n",
    "            longest\n",
    "        F[-i-1] = longest\n",
    "    return F\n",
    "</pre>\n",
    "\n",
    "\n",
    "# Алгоритм Бойера-Мура\n",
    "\n",
    "Теперь мы (надеюсь) готовы к тому, чтобы добавить эвристику \"хорошего суффикса\" к основному алгоритму.\n",
    "\n",
    "Добавим новую эвристику в алгоритм Бойера-Мура-Хорспула:\n",
    "\n",
    "\n",
    "<pre>\n",
    "BoyerMoore(pattern, text):\n",
    "    lp = Length(pattern)\n",
    "    lt = length(text)\n",
    "\n",
    "    <b>if</b> lp == 0 <b>or</b> lt == 0 <b>or</b> lt < lp:\n",
    "        <b>return</b> []\n",
    "        \n",
    "    matches = <b>new</b> List\n",
    "    \n",
    "    bad_character = BadCharacterTable(pattern)\n",
    "    L = GoodSuffixTable(pattern)\n",
    "    F = FullShiftTable(pattern) \n",
    "    \n",
    "    previous_k = -1\n",
    "    shift = 0\n",
    "    \n",
    "    <b>while</b> i >= 0 <b>and</b> shift + i > previous_k <b>and</b> pattern[i] = text[shift+i]:\n",
    "        i -= 1\n",
    "        \n",
    "    <b>if</b> i == -1 <b>or</b> shift + i == previous_k:\n",
    "        matches.append(shift)\n",
    "    <b>else</b>:\n",
    "        char_shift = i - bad_character[AlphabetIndex(text[shift + i])][i]\n",
    "        <b>if</b> i + 1 == lp:\n",
    "            suffix_shift = 1\n",
    "        <b>else if</b> L[i+1] == -1:\n",
    "            suffix_shift = lp - F[i+1]\n",
    "        <b>else</b>:\n",
    "            suffix_shift = lp - L[i+1] - 1\n",
    "        max_shift = Max(char_shift, suffix_shift)\n",
    "        <b>if</b> max_shift == i+1:\n",
    "            previous_k = matches[-1] <b>if</b> matches <b>is not</b> empty <b>else</b> -1\n",
    "        shift += max_shift\n",
    "        \n",
    "    <b>return</b> matches\n",
    "        \n",
    "    \n",
    "</pre>\n",
    "\n",
    "\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "\n",
    "# Сложность алгоритма\n",
    "\n",
    "* O(n) с учетом соблюдения правила Галила.\n",
    "\n",
    "\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "\n",
    "# Домашняя работа\n",
    "\n",
    "* Реализовать функцию `FindPrefixes`, которая создает таблицу префиксов (примеры работы есть в материале):\n",
    "  * Функция `FindPrefixes(string)` возвращает массив `Z` такой, что `Z[i]` содержит число, равное длине подстроки `S[i]`, котороая при этом является и префиксом `S`. Если такого префикса нет, то `Z[i]` будет 0.\n",
    "\n",
    "  * Есть тест-кейсы: файл `preprocess_test_cases.txt`. Традиционно, первая строка - количество записей, первый столбец - строка, далее через пробел - записанные последовательно значения из таблицы префиксов для данной строки. \n",
    "* Реализовать алгоритм Бойера-Мура-Хорспула. \n",
    "  * Для проверки есть тест-кейсы: `string_matching_test_cases.tsv`. Это .tsv без заголовка, но в первой строчке общее число записей. Первый столбец - текст (содержит пробелы), после первого таба - столбец с паттерном (также может содержать пробелы), в последнем столбце через пробел _все_ вхождения паттерна в текст, в том числе, перекрывающиеся частично. Если вхождений нет, столбец пуст.\n",
    "* (для тех, кто пишет на C): проверить работу алгоритма Бойера-Мура, реализация на С, [из википедии](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm#Implementations). Можно на предложенных тест-кесах, можно на своих.\n",
    "\n",
    "\n",
    "* [Реализация на Go](https://golang.org/src/strings/search.go)\n",
    "\n",
    "</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
