Last active
October 27, 2019 14:52
-
-
Save Lerbytech/4d195dbb68b70bfe1d5c627b7699d91f to your computer and use it in GitHub Desktop.
Машина Тьюринга
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<stdio.h> | |
#include<string.h> | |
const int N = 10; | |
// структура, описывающая результат выполениия состояния, а именно: | |
// | |
struct ReturnType | |
{ | |
struct ReturnType(*ResultState)(int); // это - указатель на функцию. Указывает на следующее состояние, что следует выполнить машине. | |
int СellVal; // указывает на текущее значение измененного элемента ленты | |
int MachineStep; // Шаг головки: вправо, влево, остались на месте. +1 -> Right, -1 -> left, 0 - stay | |
char StateName[40]; // название метода для удобства вывода на экран | |
}; | |
// структура, описывающая саму нашу машину | |
struct Machine | |
{ | |
struct ReturnType(*MachineState)(int); // это указатель на функцию. Описывает текущее состояние машины. | |
int HeaderPos; // координата головки | |
char MachineStateName[40]; // название состояния для удобства вывода на экран | |
}; | |
//-------------------------------------------------- | |
//--------------------PRINTING---------------------- | |
//-------------------------------------------------- | |
// вывод ленты | |
void PrintCells(int cells[], int n) | |
{ | |
for (int i = 0; i < n; i++) | |
if (cells[i] < 0) | |
printf("#"); | |
else printf("%d", *(cells + i)); | |
printf("\n"); | |
} | |
// хитрый способ вывода позиции головки в ленте. | |
void PrintHeaderPos(struct Machine m) | |
{ | |
for (int i = 0; i < m.HeaderPos; i++) | |
printf("%c", ' '); | |
printf("^\n"); | |
} | |
// объединенная функция печати | |
void PrintAll(int cells[], Machine M) | |
{ | |
printf("State> %s \n", M.MachineStateName); | |
PrintCells(cells, N); | |
PrintHeaderPos(M); | |
} | |
//-------------------------------------------------- | |
//--------------CELLS PREPARE ----------------------- | |
//-------------------------------------------------- | |
// в этой функции заполняете ленту так, как вам нужно для задачи. | |
// заведите несколько функций если решите в одной программе слепить разные задачи воедино | |
void PrepareCellsForWork(int cells[]) | |
{ | |
for (int i = N - 2; i >= N - 5; i--) | |
cells[i] = 1; | |
} | |
// массив заполняем нулями для гарантии. можно совместить с PrepareCells | |
void InitCells(int cells[]) | |
{ | |
for (int i = 0; i < N; i++) | |
cells[i] = 0; | |
} | |
//-------------------------------------------------- | |
// финальнео состояние, после которого точно завершим работу. | |
struct ReturnType Q_END(int val) | |
{ | |
ReturnType temp = { NULL, 0, 0, "Q_END" }; | |
return temp; | |
} | |
// состоняние "зануления" | |
struct ReturnType Q_Nullate(int val) | |
{ | |
ReturnType RES; | |
// если в ячейке 0, то завершаем работу | |
if (val == 0) | |
{ | |
RES.ResultState = Q_END; | |
RES.СellVal = 0; | |
RES.MachineStep = 0; | |
strcpy(RES.StateName, "Q_NULLATE"); | |
} | |
// если в ячейке 1, то заменим на 0 и сдвинемся влево. При этом состояние машины останется прежним | |
if (val == 1) | |
{ | |
RES.ResultState = Q_Nullate; | |
RES.СellVal = 0; | |
RES.MachineStep = -1; | |
strcpy(RES.StateName, "Q_NULLATE"); | |
} | |
return RES; | |
} | |
// стартовое состояние. Именно из него машина начинает работу | |
struct ReturnType Q_Start(int val) | |
{ | |
// в стартовом состоянии мы НЕ меняем значение текущей ячейки и позицию, только Состояние | |
ReturnType RES = { Q_Nullate, val, 0, "Q_Start" }; | |
return RES; | |
} | |
// ГЛАВНАЯ ФУНКЦИЯ!!! ВСЯ РАБОТА СОВЕРШАЕТСЯ ЗДЕСЬ!!! | |
// написана универсально, не думаю что её вам придется менять. | |
// если придумаете как улучшить - отпишитесь мне пожалуйста и расскажите как вы это сделали. | |
// общую идею - читайте в описании к этому файле ниже на страничке | |
// если на пальцах: берем ленту и машину. Вызываем метод реализующий логику работы текущего состояние. | |
//Получаем результат - применяем его к машине: | |
// 1 - меняем значение текущей ячейки в ленте | |
// 2 - сдвигаем головку | |
// 3 -переписываем состояние машины и его(состояния) название | |
void RunMachine(int cells[], Machine M) | |
{ | |
ReturnType MidResult; // структура для хранения типа | |
int step_num = 0; // порядковый номер шага для удобства вывода на экран | |
// пока не дойдем до конечного состояния работаем | |
while (M.MachineState != Q_END) | |
{ | |
// вызываем метод состояния на выполнение. Передаем ему значение текущей ячейки ленты | |
MidResult = M.MachineState(cells[M.HeaderPos]); | |
// применяем результаты к нашей машине | |
cells[M.HeaderPos] = MidResult.СellVal; | |
M.HeaderPos += MidResult.MachineStep; | |
M.MachineState = MidResult.ResultState; | |
strcpy(M.MachineStateName, MidResult.StateName); | |
//условие для выхода чтобы не печатать на экран лишний раз (возможно костыль) | |
if (M.MachineState == Q_END) break; | |
// лента конечна - проверяем на границы массива. | |
// если ваша программа точно работает верно - можете удалить | |
// написал чисто для вашего удоства пока будете отлаживать код | |
if (M.HeaderPos < 0 || M.HeaderPos >= N) | |
{ | |
printf("ERROR!!!! MachinePos is out of range!!! \n"); | |
break; | |
} | |
// выводим на экран текущее состояние программы | |
printf("Step # %d \n", step_num); | |
step_num++; | |
PrintAll(cells, M); | |
} | |
} | |
//------------------------------------------------------ | |
//------------------------------------------------------- | |
// УЧЕБНЫЙ ПРИМЕР: напишем машину, которая зануляет все 1 | |
int main() | |
{ | |
int cells[N]; // наша лента | |
struct Machine M; // наша машина | |
// подготовим ленту | |
InitCells(cells); // занулим | |
PrepareCellsForWork(cells); // заполним значениями под задачу зануления. получим вот такое: 0000011110 | |
// Установим головку машины на нужное место | |
M.HeaderPos = N - 2; // кон | |
// переведем машину в стартовое состояние | |
M.MachineState = Q_Start; | |
//--------- | |
// запустим | |
RunMachine(cells, M); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Машина Тьюринга - расширение идеи конечного автомата. МТ способна имитировать любой алгоритм, если его можно представить в виде набора правил, реализующих процесс пошагового вычисления, и в котором каждый шаг вычисления достаточно элементарен.
То есть, всякий интуитивный алгоритм может быть реализован с помощью некоторой машины Тьюринга.
Исторический ликбез для общего развития
В 30х-40х годах 20 века очень активно начала развиваться автоматизация. Создавалось множество электронных устройств, бравших на себя задачи оператора. Зачастую это были электромеханические механизмы, которые в силу своей конструкции были способны выполнять фиксированный и жестко заданный набор действий. Это было удобно, но не совсем - разрабатывать миллион роботов под миллион задач само себе идея глупая. Творческая мысль изобретателей шла дальше и было предложено делать решающие устройства, которые изменяли последовательность действий в работе устройства. Так, например, были автоматизированы телефонные станции: если раньше абонентов соединяли девушки перетыкивая провода в нужные гнеза, то теперь достаточно было набрать номер и АТС сама распознает его, преобразует в сигналы, поймет последовательность цифр и свяжет два нужных реле так, чтобы абоненты смогли услышать друг друга. Тем не менее, программа действий АТС всё ещё заложена в её конструкцию и по сути - жёстко задана (так как эти действия можно писать решающим деревом)
Это было значительным достижением, но творческая мысль шла дальше. Возник вопрос - любой ли алгоритм можно переложить на автоматику? Можно ли сделать так, чтобы аппаратура, или исполнитель, сама в ходе работы изменяла своё поведение в зависимости от исходных данных и промежуточных результатов работы?
Алан Тьюринг предложил теоретическую модель Машины.
Машина состоит из:
Изначально робот устанавливается на нужную точку ленты и ему задается стартовое состояние. Выполняя состояние - то есть, исполняя описанную им последовательность действий - робот может изменять своё положение на ленте (влево или вправо на 1 позицию, или остаться на месте), изменять значение той ячейки на которую он сейчас смотрит или переходить в другое состояние.
Задавая последовательно состояний, мы можем описать любой алгоритм.
Пример: пусть задана лента
00011111000
где 0 - пустые ячейки. Стартовая позиция головки - самая правая 1. Мы хотим поставить головку на самую левую единичку. По формулировке МТ головка видит только значение текущей ячейки, а потому - непонятно какая ячейка самая левая. Для решения этой задачи построим цепочку состояний - элементарных действия.Введём таблицу состояний:
Пусть в начале работы мы стартовая позиция головки - самая правая 1. Стартовое состояние - Q_Start
Как видно из таблицы, результатом выполнения состояния будет состояние Q_GoLeft.
На следующем шаге робот смотрит на видимую ячейку. Видит 1 - по условию в таблице смещается влево на 1. Состояние остается прежним - Q_GoLeft.
На следующем шаге робот всё ещё в состоянии Q_GoLeft. Он снова видит единичку и сдвигается влево. Далее ситуация повторится.
Так будет продолжаться до тех пор, пока робот не окажется левее самой левой 1, то есть не увидит 0.
По таблице - если в состоянии Q_GoLeft робот увидит 0, перейдет в состояние Q_GoStepRight.
В Q_GoStepRight робот независимо от значения ячейки выполнит 1 шаг вправо и перейдет в Q_END
Достигнув состояния Q_END мы завершим работу.
Псеводокод:
Обратите внимание: сложную задачу (сместиться на самую левую 1) мы выразили через последовательность простых, "дубовых" элементарных действий. В этом и состоит суть Машины Тьюринга - если алгоритм можно представить в виде таких элементарных, "атомарных" операций, то и алгоритм в целом может быть вычислен на устройстве, аналогичном МТ.
Подведем итог: чтобы реализовать МТ необходимо написать логику, реализующую состояния, и научиться в коде переходить от одного состояния к другому.
В исходном коде к этой статье представлена реализация задачи обнуления: мы хотим обнулить всю заданную последовательность 1.
То есть из
0000011110
получить0000000000
Сразу прикреплю поэтапный вывод работы МТ согласно коду с данной страницы:
По коду:
Для работы я ввёл две структуры:
ReturnType
иMachine
.Структура
Machine
описывает нашу машину. Она хранит только текущее состояние и своё положение на ленте.MachineStateName
- текстовое название текущего состояния - хранится в машине же для удобства работы с функцией печати на экран.(Формально,
Machine
должна хранить значение текущей ячейки и сама применять метод состояния. Но я не придумал красивого способа сделать это в рамках Си без привлечения ООП или темной магии. Возможно я проглядел простое решение (или просто затупил); буду рад вашим дополнениям)Структура
ReturnType
описывает результат выполнения состояния: нового состояниеResultState
, нового состояние ячейкиCellVal
, указания куда была сдвинута головкаMachineStep
и названия состояния для вывода на экран этапов работы.Чтобы понять почему
ResultState
иMachineState
так странно выглядят - прочитайте главу про указатели на функции из отличного учебника по Си:https://metanit.com/cpp/c/5.11.php
<не, серьёзно, прочитайте, я не буду это объяснять>
Итак, если прочитали, то продолжим.
Каждому состоянию соответствует некоторый код. Я обернул каждый такой кусок кода в функцию. Обсчитывая любое состояние мы вызываем соответствующую ему функцию в нашей программе. Для унитарности кода и удобства работы с состояниями, а именно - легкого переключения между ними без громоздких
switch
- я написал всё на указателях на функцию. Самостоятельно вам нужно лишь написать свои функции состояний, сохраняя шаблон: принимаемint
и возвращаемReturnType
.Состояние рассчитывается только для текущей ячейки ленты - поэтому все функции принимают число типа
int
.Любое состояние в ходе работы может сдвинуть ячейку, сменить значение входной ячейки и/или приказать перейти в другое состояние. Все эти данные хранятся в структуре
ReturnType
. Все функции состояний возвращают переменные данного типа.Код лучше тысячи слов - рассмотрите устройство функций
Q_Nullate
,Q_Start
иQ_END
чтобы понять их устройство и механику работы.Вся соль программы в функции
RunMachine
.В цикле
while
, пока состояние машины не станет равно Q_END мы вызываем функциюMachineState
структурыMachine
передавая ей значение текущей ячейкиcells
по координатам машиныHeaderPos
. И получаем результат в виде структурыMidResult
.Затем, все значения
MidResult
взаимодействуют с структуройMachine
и лентойcells
: обновляются все необходимые значения. Прочитайте код, всё будет очень понятно.Таким образом, когда вы будете писать свою программу, вам необходимо будет написать свои функции для тех состояний, что вы ввели для решения вашей задачи. Они должны быть схожими по устройству с
Q_Nullate
если вы хотите легко интегрировать их в код выше.RunMachine
написана достаточно универсально, менять её вам не придется (скорее всего).Черновики решения задач сложения двух чисел и умножения их на два есть у Насти Золотухиной.
Если возникнут вопросы по коду, этой статье либо просто заметите опечатку - пишите мне в вк или телеграме.
Напоследок могу порекомендовать сайт: http://turingmachine.io/
Здесь вы найдете визуализацию работу МТ и пример скрипта для работы с ней. Код в статье был написан под безусловным влиянием данного сайта.
Дерзайте!
p/s Если нужно увеличить длину ленты - в строчке 2 поменяйте значение
N
.p/p/s Есть недокументированная особенность кода: если вы захотите реализовать алгоритм с использованием временных, служебных символов, то вам понадобится отображать их на экране. В функции вывода ленты на экран предусмотрено любое отрицательное число выводить как
#
. Поэтому в функциях состояний для установки служебного символа записывайте в ленту любое отрицательное число. Можете доработать на своё усмотрение.