Доказательство Тьюринг-полноты однострочников на Python

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Написание однострочников в Python всегда было довольно интересным для меня, и однажды я заинтересовался - а любой ли алгоритм возможно реализовать всего в одну строчку Python кода ?

Оказалось - да!

Немного теории

Исполнитель называется Тьюринг-полным, если на нём можно реализовать любую вычислимую функцию, и наоборот. То есть, чтобы доказать что в одну строку на Python можно написать какой угодно код, необходимо доказать Тьюринг-полноту однострочных программ на python. Как это сделать ?

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

Чёткие правила данного автомата изложены в Википедии по ссылке здесь.

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

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

Реализация

Сразу уточню, что символ ";" я считал началом новой строки, чем он по сути и является, иначе задача превращается в тривиальную.

Итак, опредилившись с тем что же именно я собираюсь написать, я, недолго думая, запустил VSCode и... И завис. Потому что неясно было что писать. Ясно было, что нужен цикл while, ввод начального состояния, которое нужно как-то обработать, действия над ним в цикле, Кроме того, цикл надо ещё как-то остановливать, если состояние системы стабилизировалось и больше не меняется.

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

# А не работает так!
while (a=int(input()) or a!=0):print(a)

На некоторое время я оставил свою идею, пока, читая хабр, вдруг не наткнулся на следующую мысль - для объявления переменной в одну строку в python можно использовать глобальные переменные. Например, вот так:

#Объявление
globals.update({"a":0})
#Получение значения
globals.get("a")
# Простейший цикл while
while (globals.update({"a":int(input())}) or globals.get("a")!=0):print(globals.get("a"))

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

В целом, достаточно сложная задача и была решена при помощи этих двух приёмов - глобальные переменные и фиктивный условный оператор. Исходники лежат здесь, если кому-то интересно, можно посмотреть, а сейчас я немного распишу как итоговый код работает.

https://github.com/Thane784/rule_110

Куда же тыкать ?

Основной файл - code.py Там, правда есть некоторые проблемы с выводом - для человека он не очень читаем. Можно запустить файл show.py , там цикл искусственно ограничен 25 итерациями и всё видно нагляднее.

Итоговый код и как он работает

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

Для этого заведем две глобальные переменные "s" (string) и "l" (last). Да, я знаю, что s и l - не очень хорошие названия для переменных, но для одноразового кода, который никто в дальнейшем поддерживать не будет, думаю нормально.

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

# Записываем нач. состояние в переменную,
# выводим его же в консоль, 
# причём только если предыдущего состояния нет,
# то есть на первой иттерации цикла,
# используем оператор or для исполнения кода.
# По факту в сравнение пойдет лишь globals().get("s")),
# которое мы и сравниваем с предыдущим состоянием globals().get("l"))
while (( ( globals().update({"s": input()}) or print(globals().get("s"))) if globals().get("l") == None else None) or globals().get("s")) != globals().get("l"): print('')t('')

Далее, уже в теле цикла мы объявляем фиктивную переменную a которой присваиваем значение длинного длинного сравнения через оператор or. На самом деле, оператор сравнения здесь нужен чтобы выполнить большое количество исполняемого кода, в котром каждая вызываемая функция вернёт None. Возможно, можно было реализовать подобное более красиво, но это самое быстрое рабочее решение, которое пришло мне в голову.

#Скелет
while last_code: a = (func1() or func2() or func3() or 1)

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

Для хранения состояния используем строку, постепенно обрабатывая её, собираем массив, который содержит новое состояние. Этот массив затем приводим к строке при помощи функции ''.join() и перезаписываем глобальную переменную s. Скорее всего, можно было бы обойтись без массива, но оптимизация здесь не слишком важна. Также, при определённых условиях расширяем наблюдаемую область(если в результате работы алгоритма "оживились" клетки, ранее нами не отслеживаемые(поле, естесственно бесконечно), откуда кстати и проблемы с выводом - довольно скоро поле становится ОЧЕНЬ большим). Большую часть кода занимает вычисление следующего состояния на основе предыдущего, это просто множество условий if elif else записанных в одну строчку.

Вот так выглядит финальный код.

while (( ( globals().update({"s": input()}) or print(globals().get("s"))) if globals().get("l") == None else None) or globals().get("s")) != globals().get("l"): a = ( globals().update({"l": (globals().get("s"))}) or globals().update({"s": (''.join(['1' if globals().get("s")[0] == '1' else '']+[('0' if (n != 0 and n!=(len(globals().get("s"))-1) and (globals().get("s")[n-1:n+2] == '000' or globals().get("s")[n-1:n+2] == '100' or globals().get("s")[n-1:n+2] == '111') ) else '0' if (n == (len(globals().get("s"))-1) and ( ((globals().get("s")[-2] if len(globals().get("s"))>1 else '0')+globals().get("s")[-1]) == '00' or ((globals().get("s")[-2] if len(globals().get("s"))>1 else '0')+globals().get("s")[-1]) == '10') ) else '0'  if (n == 0 and globals().get("s")[0]+(globals().get("s")[1] if len(globals().get("s"))>1 else '0') == '00'  ) else '1') for n in range(0,len(globals().get("s")))]) )})  or (print(globals().get("s")) if globals().get("s") != globals().get("l") else None) or 1) or 1)

Что же в итоге ?

Итак ! Мы написали в одну строчку Тьюринг-полную систему, чем доказали, что однострочники на python обладают Тьюринг-полнотой. Что же это значит ? Это значит, что в одну строчку python кода можно написать всё что угодно ! Даже Windows 11 или ядро Linux.

Правда, скорее всего, код получится слегка нечитаемым да и дебаггинг будет затруднён...)

P. s.

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

Источник: https://habr.com/ru/post/588955/


Интересные статьи

Интересные статьи

Мы рады сообщить, что стала доступна августовская версия расширения Python для Visual Studio Code. Вы можете загрузить расширение Python из Marketplace или установить его прямо из галереи расширений в...
В этом посте мы заглянем под капот алгоритмов компьютерной графики, пошагово разберем основные принципы трассировки лучей и напишем ее простую реализацию на Python. Никак...
Введение В данной статье я бы хотел продемонстрировать то, как можно реализовать собственную программу ARP-спуфинга на Python. Реализаций уже тысячи, но почти все они с использование...
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
Примеры многомерных графиков Введение Визуализация — важная часть анализа данных, а способность посмотреть на несколько измерений одновременно эту задачу облегчает. В туториале мы будем рисоват...