Простейшие самообучающиеся алгоритмы на языке «Автор»

  • Tutorial
К сожалению, все самообучающиеся алгоритмы достаточно сложны и объёмны. Логика самообучения алгоритма по-прежнему остаётся задачей программиста. Но, не смотря на это, я приведу пример классической задачи на самообучение.
Задача состоит в том, чтоб дать ответ, является ли заданное число простым. На первый взгляд ничего особенного. Но проблема заключается в том, что для того, чтобы дать правильный ответ, нужно найти заданное число в ряде простых чисел. Другого способа просто не существует. А этот ряд бесконечный. Разумеется, мы можем задать в памяти только ограниченный отрезок ряда, и получается, что как не крути, а заданное число может превышать ограничение. Конечно, вы скажите, что ряд легко продолжить. Да, вы абсолютно правы. Но с точки зрения оптимизации работы программы, по времени, неудобно каждый раз вычислять одно и тоже, а лучше сохранять массив простых чисел, каждый раз при дополнении его. Собственно в этом и заключается мера обучения этой классической задачи. С точки зрения классического программирования нам нужно организовать хранилище для содержания массива известных простых чисел, например в отдельном файле, или в базе данных. Но опираясь на возможность языка «Автор» вносить изменения программ в собственный код можно сделать такое хранилище прямо внутри кода программы.
// prosto.txt
// простейшая самообучающаяся программа поиска простых чисел.
var isProsto(n){
	m={2,3,5,7};
	if(n<0)return #;
	ok=0;
	if(n==0 || n==1)ok=1;
	for(i=0;i<m.size();++i){
		if(n==m[i]){ok=1;break;}
		if(n<m[i])break;
		}
	if(i==m.size()){
		u=m[i-1];
		oknew=0;
		do{
			++u;
			uok=1;
			for(i=0;i<m.size();++i)if(!(u%m[i])){uok=0;break;}
			if(uok){
				oknew=1;
				m.push(u);
				if(u==n)ok=1;
				}
			}while(n>u);
		if(oknew){
			f=getFunction(getThisFunctionName());
			pos=f.Root();
			pos=f.Next(pos);
			f.setComand(pos,"m="+m.export());
			}
		}
	return ok;
}

void main(){
	ok=isProsto(n=200);
	trace("Число "+n+(ok?"":" не")+" простое.");
}

Вся задача заключается в вызове функции «isProsto(n)» с заданным числом для анализа. Собственно в переменной «m» и содержится наше хранилище. Для замены команды в схеме алгоритма используется функция «f.setComand(pos,«m={2,3}»);» которую нужно вызвать от объекта функции. Первым параметром нужно указать идентификатор узла с командой в граф-схеме алгоритма, которую(команду) следует заменить, а вторым объект команды (дерева операторов). Вторым параметром может быть и строка текста, которая неявно преобразится (распарсится). Для того, чтоб получить идентификатор узла воспользуемся тем фактом, что массив/хранилище находится на первом узле от начала алгоритма функции. Функция «f.Root()» вернёт идентификатор первого и последнего узла схемы, так сказать узел началоконца алгоритма. Из него(узла) можно перейти на, гарантировано, один, первый узел. А вот поднимаясь вверх из первого и последнего узла («f.Up(pos)») возможно получить множество (массив идентификаторов) узлов, которыми заканчивается алгоритм. Дело в том, что в конце алгоритма может располагаться условный оператор с ветвлением, ведущим в узел началоконца.
Посмотрим, во что превратилась наша функция после запуска программы.
// prosto.code
void isProsto(var n){
	m={
		2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,
		67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,
		139,149,151,157,163,167,173,179,181,191,193,197,199
		};
	if(n<0)return # ;
	ok=0;
	if(n==0||n==1)ok=1;
	i=0;
	for(;i<m.size();++i){
		if(n==m[i]){
			ok=1;
			break;
			}
		if(n<m[i])break;
		}
	if(i==m.size()){
		u=m[i-1];
		oknew=0;
		do{
			++u;
			uok=1;
			i=0;
			for(;i<m.size();++i)if(!u%m[i]){
				uok=0;
				break;
				};
			if(uok){
				oknew=1;
				m.push(u);
				if(u==n)ok=1;
				}
			}while(n>u);
		if(oknew){
			f=getFunction(getThisFunctionName());
			pos=f.Root();
			pos=f.Next(pos);
			f.setComand(pos,"m="+m.export());
			}
		}
	return ok;
}

В языке «Автор» также есть возможность использования меток, по которым можно найти идентификаторы соответствующих им узлов в схеме алгоритма. Каждая метка содержит номер, который должен быть уникальный в приделах функции.
Рассмотрим следующую задачу. В программах бывает так, что нужно выполнить какие-то сложные вычисления с константами, которые занимают время, и которые достаточно сделать только один раз, скажем при написании программы, чтоб не тратить время каждый раз при новом запуске на одни и те же вычисления. Посмотрим, как можно использовать возможность языка, трансформировать скрипт, для решения этой задачи.
// one.txt
void main(){
	trace("Helloy World!!!");
	<label:10>
	if(1){
		x=1+1; // сложные вычисления
		f=getFunction(getThisFunctionName());
		pos=f.getLabel(10); // ищем метку
		pos=f.insertDown(pos);
		f.setCommand(pos,"x="+x);
		pos=f.Down(pos);
		command=f.getCommand(pos);
		command.setSub({0},PROGRAM("0"));
		f.setCommand(pos,command);
		}
	trace(x);
	getstring();
}

В данном случае, программа, конечно же, выдаст на экран «2». Но посмотрим, как выглядит файл дубликат кода, из которого интерпретатор будит брать код программы, уже после первого запуска.
// one.code
void main(){
	trace("Helloy World!!!");
	<label:10>
	x=2;
	if(0){
		x=1+1;
		f=getFunction(getThisFunctionName());
		pos=f.getLabel(10);
		pos=f.insertDown(pos);
		f.setCommand(pos,"x="+x);
		pos=f.Down(pos);
		command=f.getCommand(pos);
		command.setSub({0},PROGRAM("0"));
		f.setCommand(pos,command);
		}
	trace(x);
	getstring();
}
// one.code	:-|

Конечно же, всё можно организовать и по другому, в зависимости от сложности задачи. Я просто хотел продемонстрировать перспективы, которые открываются с возможностью программ изменять собственный код.
Также в языке есть особая системная функция «Spirit();», которая само удаляется при первом своём исполнении. Она принимает имя функции и аргументы к ней. Так что получается, что указанная функция вызовется только однажды и от этого не останется никаких следов.
// Spirit.txt
firstprocess(namef,n){
	x=100*(1+1);
	f=getFunction(namef);
	pos=f.getLabel(n);
	f.setCommand(pos,PROGRAM("k="+x));
	return 1;
}

void main(){
	Spirit("firstprocess",getThisFunctionName(),10);
	<label:10>
	trace("k="+k);
	getstring();
}

Эта программа выведет на экран «k=200» и вот во что превратится.
// Spirit.code
int firstprocess(var namef,var n){
	x=100*(1+1);
	f=getFunction(namef);
	pos=f.getLabel(n);
	f.setCommand(pos,PROGRAM("k="+x));
	return 1;
}

void main(){
	k=200;
	trace("k="+k);
	getstring();
}
// Spirit.code	:-|


Интересно(?) | Следует продолжение

Комментарии 25

    +3
    Было бы здорово, если бы в начале/в конце статьи вы бы кратко перечисляли возможности, о которых пойдет речь.
    Например, так:
    • Первый пример показывает, как программа может изменять значения инициализации массива в своем собственном коде
    • Второй пример показывает, как программа может выполнить лишь раз определенный фрагмент своего кода, а затем удалить его

    По самой идеи: у вас получается крайне неустойчивая система. Я и в свой-то код стараюсь без необходимости не лезть, а если в него еще и сам компьютер будет вносить правки, это будет будет просто ужасно. Может вам стоит подумать про версионность? Во всяком случае на текущий момент, как я думаю, поддерживать код на вашем языке будет крайне проблематично.
      +2
      А зачем вообще программе модифицировать свой код? В приведённых случаях достаточно просто записывать данные в файл, а не в код программы. Получаем разделение данных и инструкций, а это легче понимать, чем самомодифицируемый код.
        +2
        Вдогонку: переименуйте isProsto, пока никто не увидел :)
          –8
          Имена не существенны, важна только любовь.
            0
            Любовь к именам.
        +2
        И ни одной ссылки на сам язык… Иди, читатель, сам гугли!
        +2
        Оформление программ отвратительное. Ни пробелов, ни нормальных отступов. Глаза ломаются.
          +1
          В чем преимущество по сравнению с хранением найденных простых чисел в обычном хранилище?
            0
            Ни в чём. Всё дело в авто модификации алгоритма.
              0
              А у вас никакой модификации алгоритма не происходит, у вас меняются данные.
                0
                Весь алгоритм для языка (операторы, условия, циклы) — данные. Вы правы, всегда изменить можно только данные.
                  0
                  Весь алгоритм для языка (операторы, условия, циклы) — данные.

                  Только в том случае, если у вас фон Неймановская архитектура.

                  Вопрос преимущества остается открытым. Какой смысл брать новый язык, когда все то же самое можно сделать на десятке старых?
                    0
                    Я не знаю аналогов для функции Spirit();
                      0
                      А какая от нее польза?
                        –1
                          0
                          А словами описать никак?
                            0
                            Лучше один раз увидеть, чем сто раз услышать).
                              0
                              Я бы предпочел один абзац текста, в котором было бы описано, в каких случаях без этой функции не обойтись.
                                0
                                Дело в том, что существует позиция в схеме алгоритма, которая указывает на текущую команду. По этому можно удалять любую часть алгоритма, кроме той, в которой находится текущая команда удаления. Именно для решения этой проблемы существует Spirit(), который само удаляется, как одна команда, сразу после своего исполнения.
                                  0
                                  Во-первых, эта «проблема» («можно удалять любую часть алгоритма, кроме той, в которой находится текущая команда удаления») присуща только вашему языку, в общем случае ничто не мешает удалить любую часть кода.

                                  Во-вторых, я вообще не очень понимаю, какую задачу решает удаление части алгоритма в момент выполнения.
                                    –1
                                    Задачу само оптимизации программы.
                                      0
                                      Во-первых, самооптимизация — это не задача, это средство решения задачи
                                      А во-вторых, достаточно исключить ветвь алгоритма из выполнения, и эффект достигнут.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое