Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Полезное программирование

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
191Бредогенератор. Сначала немного о ЛИСПе
круглые скобки на квадратные, но мне круглые кажутся более наглядными, по сугубо сентиментальным соображениям. Об этом — в следующем разделе.
Бредогенератор. Сначала немного о ЛИСПе
Первое, что приходит на ум — а зачем мы это вообще делаем? Точнее, зачем
мы это делаем, понятно — просто от безделья. Но зачем мы это делаем своими собственными руками?
Всё уже украдено до нас! © Операция Ы Действительно, куда ни плюнь, всё уже украдено до нас. Точнее, всё уже при-
думано до нас, и запрограммировано тоже.
Давным-давно, точнее в пятьдесят каком-то позднем году, уже был создан
язык обработки списков LISP (LISt Processing language). Список, в понимании этого языка — то, что в круглых скобках. Типичная лисповская программа про­сто кишит и рябит в глазах круглыми скобками.
Вот образчик. Хотя это извлечено из абсурдопедии http:// absurdopedia.wikia.
com/wiki/ЛИСП, но в чём-то они правы.
// Цитата
Типичный текст на лиспе:
(((()))(()(((()))(()))((()))((())))(((()(()()(()()()(())((()))))))))
Некоторые отступники пытаются испортить простоту и изящность лиспа символамипробелаи переноса строки. Текст на лиспе, написанный отступником:
((()(((
() () (( () (())) (()))
()))
)(() )))
// конец Цитаты
Приглядевшись под микроскопом, замечаем типичную конструкцию — скоб-
ка открывается, имя функции, параметры, скобка закрывается. Любой из пара­метров может выглядеть точно так же, то есть — скобка открывается… Сразу надо предупредить, что настоящий, не фальшивый ЛИСП не разделяет программу и данные, поступающие на вход программы. Это одно и то же.
Пример. Поскольку нас интересуют только строки, то все примеры будут
только со строками. Хотя в чистом ЛИСПе кроме строк ничего нет, да и строк нет, честно говоря.
(StrCat “a” “b” “c”)
192 Глава 10. Для дома, для семьи
На выходе
“abc”
Что и понятно, функция StrCat склеивает несколько строк в одну. Нам, для
наших целей, это не нужно и неинтересно. Но скобки могут быть и вложенными, а это уже обещает быть более полезным. Например, вот такой программный код:
(StrCat “a”, “b”, (StrCat “c”,”d”))
Результат
“abcd”
Здесь о ЛИСПе пока всё, а дополнительно почитать можно вот что:
Альфред Ахо, Рави Сети, Джеффри Ульман, Моника Лам
«Компиляторы. Принципы, технологии, инструменты» 2008.
Это второе издание, первое вышло ещё в 1988-м, но с тех пор ничего не изме-
нилось. Читайте всё, когда-нибудь пригодится. А ЛИСП я изучал вот по этой книге:
С.С. Лавров, Г.С.Силагадзе «Автоматическая обработка данных. Язык лисп и его реализация», М.»Наука», 1978
// Теоретическое рассуждение
Если поглядеть на нашу задачу немного под другим углом, то мы увидим вполне типичную работу для программиста— простую или сложную, как мы выберем. Вслу­чае простой задачи мы кодируем сейчас несложный программируемый калькулятор. Вслучае сложной— мы создаём компилятор с неведомого, но вместе с тем, неслож­ного, языка.
// конец Теоретического рассуждения и раздела тоже
Бредогенератор. Реализация
Для достижения нашей цели неплохо было бы реализовать заранее несколько
простых, но, в то же время, универсальных функций работы со строками. Таких, которые нам понадобятся не только конкретно для этой разработки, но и, скорее
193Бредогенератор. Реализация
всего, позже. А почему они не написаны раньше разработчиками уважаемых, всемирно известных фирм, как-то — Borland, Microsoft, Тверьторф? Не знаю, на­верное, у них были более другие важные занятия. Функций в языке вроде бы и много, но или все не те, или работают не так. Приходится всё делать самому, вот этими самыми руками. Минимальный набор функций размещён в приложени­ях, возможно, в процессе разработки какие-то из них окажутся лишними. Книгу я пишу по народному чукотскому принципу — «Что вижу, то пою», то есть про­граммы я пишу параллельно с написанием текста главы про эту программу, по­этому время от времени в повествовании образуются зигзаги и петли.
Посмотрите на эти функции. Подумайте. Если они покажутся вам полезны-
ми, подумайте ещё раз, возможно, вам понравится. Почему функция подсчёта слов в строке разделителями считает только пробелы? Почему функция устране­ния повторяющихся пробелов устраняет только пробелы? Нельзя ли обобщить? Да, сегодня это ещё не надо. А завтра будет надо, но уже поздно.
Возвращаемся к задаче. Задача это, напоминаю, не только и не просто учеб-
ная, но пишется в основном для нашего развлечения. А это означает, что требо­вания к ней, её функциональности и помехоустойчивости заведомо понижены. Разумеется, как происходит со всеми временными программами, если только вы начнёте использовать её чаще трёх раз в неделю, ваше отношение к её надёжно­сти станет намного жёстче.
Обработка входного текста будет производиться построчно. Для нашего слу-
чая это означает, что если левая скобка открывается на второй строке, а соответ­ствующая правая закрывается на следующей третьей строке, то это плохая, не­годная скобка. Все скобки должны замыкаться в пределах одной строки. Да, мы упрощаем себе жизнь.
Контроль входных данных у нас будет минимальный. Говоря по-русски — ми-
нимальные требования к программе, это чтобы она не выдавала “Access Violation Error” и тому подобные вызывающие злобность сообщения. Тем не менее, надо озаботиться хотя бы тем, что каждой открывающей скобке соответствует закры­вающая скобка. Будем считать сумму открывающих скобок (плюс один) и закры­вающих (минус один). Если в итоге сумма не равна нулю — то увы. Но этого мало — входной текст “aaa )123( bbb” прекрасно такую проверку пройдёт. Поэто­му, состояние «увы» должно немедленно наступить и в случае, если сумма стано­вится отрицательной:
function CheckStr( stroka : string) : boolean; var parCount : integer; i : integer;
begin
result:=true; parCount:=0;
194 Глава 10. Для дома, для семьи
for i:=1 to Length(stroka) do begin if stroka[i] = ‘(‘ then parCount:=parCount + 1 else if stroka[i] = ‘)’ then parCount:=parCount - 1; if parCount < 0 then begin result:=false; Break; end; end;
if parCount <> 0 then result:=false; end;
А теперь формулируем алгоритм в целом на псевдоязыке:
ɐɂɄɅɉɈȼɋȿɆɋɌɊɈɄȺɆ ɐɂɄɅȾɈɌȿɏɉɈɊɉɈɄȺȼɋɌɊɈɄȿȿɋɌɖɅȿȼȺəɋɄɈȻɄȺ
ȼɕȾȿɅɂɌɖɉɈȾɋɌɊɈɄɍɋɋȺɆɕɆɂȼɇȿɒɇɂɆɂɋɄɈȻɄȺɆɂ ɈȻɊȺȻɈɌȺɌɖɉɈȾɋɌɊɈɄɍɂɉɈɅɍɑɂɌɖɊȿɁɍɅɖɌȺɌ±ȺɄȺɄ""" ɁȺɆȿɇɂɌɖȼɕȾȿɅȿɇɇɍɘɉɈȾɋɌɊɈɄɍɈȻɊȺȻɈɌȺɇɇɈɃ
ɄɈɇȿɐ ɐɂɄɅȺɉɈɋɄɈȻɄȿ ɄɈɇȿɐ ɐɂɄɅȺɉɈȼɋȿɆɋɌɊɈɄȺɆ
Пояснение по поводу поиска подстроки — имеется в виду поиск пары самых
внешних из вложенных скобок. То есть, для строки “aa (bb(cc)) dd” мы должны получить в ответ, в какой именно форме ответ — уточним позднее, что открыва­ющая скобка находится в позиции 4, и вся лексема имеет длину 8. Это раздел программы достаточно специфичен и не универсален, поэтому включать его в нашу маленькую библиотеку обработки строк нет смысла. Включаем её в основ­ной текст программы. Обратите сочувственное внимание на отчаянные вопро­сительные знаки — я действительно не знаю, а как?
Ɂɞɟɫɶɛɵɥɢɫɯɨɞɧɵɣɬɟɤɫɬɮɭɧɤɰɢɢɫɜɨɬɬɚɤɢɦɢɧɬɟɪɮɟɣɫɨɦ
function FindIntParStr( inS : string; var posFr, len : integer) : string;
ɧɨɹɟɝɨɩɪɢɛɢɥɩɨɩɪɢɱɢɧɚɦɤɨɬɨɪɵɟɫɬɚɧɭɬɩɨɧɹɬɧɵɦɢɞɚɥɟɟ
Зато я с самого начала написал великолепный и исчерпывающий все потен-
циальные будущие проблемы набор тестов. Так что все ошибки выявились про­сто с полпинка, после написания основной программы конечно. На всякий слу­чай, что мы должны протестировать? Какие случаи? Во-первых, когда скобок нет вообще, а в строке или одно слово, или несколько. Во-вторых, когда скобки есть, но не вложенные, как одна пара, так и несколько. Сначала, разумеется, рассмо­треть примитивный вариант, когда внутри скобок только одно слово или вообще ничего нет, потом случай, когда перед и после самых внешних скобок есть ещё что-то. И вариант с вложенными скобками. Вот такой набор:
195Бредогенератор. Реализация
abc
abc def
(a)
aaa () bbb
123 (a) 456
(ab) (cd) (ef)
(ab cd)
123 (ab cd)
(ab c def) (12 34)
((ab c def) (12 34))
(ab cd (12 34))
И это мы ещё не обращаем внимания на случаи лишних, избыточных пробе-
лов — потому что мы их потом прибьём. Попробуйте свой вариант программы, на каком по счёту тесте он сломается? Первая версия моей программы сломалась на последнем тесте, и я этим горжусь.
Однако, мы увлеклись подробностями, и забыли о главном. Откуда вообще
возьмётся у нас текст для интерпретации? Возможно из файла, возможно его на­берут руками в каком-нибудь поле ввода, предоставленном библиотеками языка программирования. Возможно вообще из космоса телепатическим способом. Это значит, что здесь и сейчас мы должны подумать о тестовой программе, кото­рая берёт на себя обязанность обеспечить требуемый текст, и об интерфейсе го­ловной процедуры, реализующей наш алгоритм.
Тестовая программа будет брать данные из файла. Что до главного интерфей-
са, то данные будут передаваться через список строк (TStringList), мне так боль­ше нравится. Никакого сужения общности при этом не происходит — список строк легко и непринуждённо заменяется на массив или на то, что ещё в состоя­нии придумать извращённый ум:
var inSL : TStringList; fName : string;
begin
inSL:=TStringList.Create;
fName:=’test-004.brd’; if FileExists(fName) then begin inSL.LoadFromFile(fName); GenerateFrom( inSL); end else ShowMessage( fName + ‘ no hai’);
// ɫɨɯɪɚɧɢɬɶɤɭɞɚɬɨɪɟɡɭɥɶɬɚɬɜɟɞɶɨɧɧɚɦɧɭɠɟɧ" //ɡɞɟɫɶɦɵɟɝɨɤɭɞɚɬɨɤɚɤɛɵɫɨɯɪɚɧɹɟɦ inSL.Free; end;
196 Глава 10. Для дома, для семьи
Проверку на присутствие файла я добавил только после нескольких ошибок в
написании его, файла, имени. Теперь у нас уже кое-что есть и мы в состоянии частично реализовать наш псевдокод.
var inS : string; stroka : string; posFr : integer; len : integer; i : integer;
begin
for i:=1 to inSL.Count do begin inS:=inSL[i-1]; inS:=CompressSpaces(inS); while (Pos(‘(‘,inS) <> 0) do begin stroka:=FindParStr( inS, posFr, len);
ɈȻɊȺȻɈɌȺɌɖɉɈȾɋɌɊɈɄɍɂɉɈɅɍɑɂɌɖɊȿɁɍɅɖɌȺɌ±ȺɄȺɄ"""
Delete( inS, posFr, len); Insert( stroka, inS, posFr); end; inSL[i-1]:=inS; end; end;
Ранее неупомянутая функция CompressSpaces занимается именно тем, о чём
говорит её имя — сжимает последовательность пробелов до только одного про­бела. Можно и без неё, но после обработки ею текст выглядит немного аккурат­нее. Теперь осталась главная мелочь — реализовать функцию, находящую вну­три себя скобки, внутри которых тоже скобки и так далее, до тех пор пока внутри скобок совсем не останется. В этот момент наша функция выбирает случайным образом одну из лексем. Если вам кажется, что это подозрительно напоминает рекурсию, то вы правы. Хотя и не хочется, но основная часть реализации алго­ритма напрашивается быть рекурсивной. Просматриваем строку, находим скоб­ки. Вызываем процедуру с параметром в виде строки, содержащим самые внеш­ние вложенные скобки. Процедура просматривает строку, если есть скобки, то вызывает рекурсивно сама себя, но уже с параметром первой встреченной под­строки со скобками. Если скобок нет, то процедура выполняет свою основную задачу — случайным образом выбирает одно слово из нескольких, содержащихся в скобках.
Всё отлично, всё должно работать! В чём засада? Засада в том, что те слова в
строке, которые находятся снаружи самых внешних скобок имеют особый ста­тус. Они просто копируются, потому, что нам, писателям бредового текста, так удобнее. Поэтому никак не получается обработать первоначальную строку на об-
197Бредогенератор. Реализация
щих основаниях — если мы передадим её в нашу рекурсивную процедуру, то из неё случайным образом будет извлечено одно слово, а нам этого не надо. Вы зна­ете лёгкий способ справиться с этой проблемой? Я знаю — написать программу без рекурсии. Она будет простая и незатейливая и сразу заработает. Но я пошёл на принцип и решил домучить рекурсивный вариант. А что если раскрывать скобки начиная не снаружи, а изнутри?
И вдруг мне говорит служанка
Моей гостиницы, гречанка:
«А что, как этот самый мальчик -
Сыночек графа Лудовико?» © Собака на сене
Получается неплохо — извлекаем самую внутреннюю пару скобок, если их
несколько, неважно какую именно, нам подойдёт любая. Отбрасываем наруж­ные скобки — вспомните, ведь внутри этой подстроки других скобок по опреде­лению нет. Выбираем одну лексему (слово) из нескольких и вставляем на место выкушенной подстроки. Когда мы покончим с самыми внешними скобками, никаких проблем не возникнет, в отличие от предыдущего варианта. Точнее, проблема одна — а зачем нам теперь рекурсия? Вспоминается давний персонаж артиста Геннадия Хазанова. Он (персонаж, не Хазанов) всю жизнь мечтал по­пасть в метро, но никак не получалось. Максимум куда ему удалось войти — мужской туалет, потому что над входом тоже светила буква «М»…Вот и я, всю жизнь мечтаю написать рекурсивную программу, и не такую, что рекурсия про­сто нужна в образовательных целях, а такую, что без рекурсии никак не обой­тись. И опять облом.
Наш общий алгоритм выглядит теперь так:
ɐɂɄɅɉɈȼɋȿɆɋɌɊɈɄȺɆ ɐɂɄɅȾɈɌȿɏɉɈɊɉɈɄȺȼɋɌɊɈɄȿȿɋɌɖɅȿȼȺəɋɄɈȻɄȺ
ȼɕȾȿɅɂɌɖɉɈȾɋɌɊɈɄɍɋɋȺɆɕɆɂȼɇɍɌɊȿɇɇɂɆɂɋɄɈȻɄȺɆɂ ɍȾȺɅɂɌɖȼɉɈȾɋɌɊɈɄȿɋɄɈȻɄɍɂȼɕȻɊȺɌɖɋɅɈȼɈ ɁȺɆȿɇɂɌɖȼɕȾȿɅȿɇɇɍɘɉɈȾɋɌɊɈɄɍɋɅɈȼɈɆ
ɄɈɇȿɐ ɐɂɄɅȺɉɈɋɄɈȻɄȿ ɄɈɇȿɐ ɐɂɄɅȺɉɈȼɋȿɆɋɌɊɈɄȺɆ
А вот функция извлечения самой внутренней подстроки внутри скобок. Не-
много коряво, но зато работает.
function FindIntParStr( inS : string; var posFr, len : integer) : string; var parCount,maxParCount : integer; i : integer;
begin
198 Глава 10. Для дома, для семьи
posFr:=0; len:=0; parCount:=0; maxParCount:=0;
for i:=1 to Length(inS) do begin if inS[i] = ‘(‘ then begin parCount:=parCount + 1; if parCount > maxParCount then begin maxParCount:=parCount; posFr:=i; end; end else if inS[i] = ‘)’ then begin parCount:=parCount - 1; end; end;
if posFr >=1 then begin result:=Copy( inS, posFr, Length(inS)); result:=Copy( result, 1, Pos(‘)’,result) ); len:=Length(result); end else result:=inS; end;
Текст основной процедуры после этого кажется до смешного простым:
var
inS : string; i : integer;
begin
Randomize;
6KRZ6/LQ6/ÃȾɨɬɨɝɨµɡɚɤɨɦɦɟɧɬɢɪɨɜɚɬɶɩɨɬɨɦ
for i:=1 to inSL.Count do begin inS:=inSL[i-1];
inS:=CompressSpaces(inS);
while (Pos(‘(‘,inS) <> 0) do begin if CheckStr( inS) then begin ProcessParString( inS); end else begin
199Бредогенератор. Доработка напильником и планы на будущее
LQ6 ¶(5525ÃLQ6
end; end;
// ȾɈɊȺȻɈɌɄȺɇȺɉɂɅɖɇɂɄɈɆ // 3RVW3URFHVVLQJLQ6ɪɚɫɤɨɦɦɟɧɬɢɪɨɜɚɬɶɩɨɬɨɦ
inSL[i-1]:=inS; end;
6KRZ6/LQ6/Ãɉɨɫɥɟɬɨɝɨµɡɚɤɨɦɦɟɧɬɢɪɨɜɚɬɶɩɨɬɨɦ
end;
PostProcessing — та, ещё не написанная, процедура, наводящая окончатель-
ный блеск на текст, полученный в результате. ShowSL — незатейливая, но очень полезная процедура для вывода списка строк через стандартный вывод сообщения.
Бредогенератор. Доработка напильником
и планы на будущее
Осталось немного — заменить подчёркивания на пробелы и удалить наш ло-
гический ноль, то есть знак равенства. Если в случае с подчёркиваниями с удоб­ством можно воспользоваться функцией из библиотеки Delphi (SysUtils. StringReplace), то для равенства ситуация чуть сложнее — удалить надо не только равенство, но и пробелы вокруг него. А пробелы могут быть не обязательно с обе­их сторон но и только слева или только справа (когда?). Обратите внимание на не совсем тривиальный интерфейс предоставленной нам системой функции. Смо­жете ли вы написать её по памяти, если вас разбудят среди ночи? Мне кажется, если функция несложная, в пределах десяти строк, лучше написать её под себя с правильным интерфейсом. Это будет быстрее, чем заучивать волшебное слово [rfReplaceAll]. Да я знаю, это не слово, а целое множество.
В любом случае, полностью обойтись казёнными или нашими средствами за-
мены символов не удастся и придётся потом вызвать опять-таки нами сочинён­ную функцию CompressSpaces. Вот результат, вставьте вызов функции туда, куда надо.
function PostProcessing( var stroka : string) : string; begin
stroka:=StringReplace( stroka, ‘_’, ‘ ‘, [rfReplaceAll]); stroka:=StringReplace( stroka, ‘=’, ‘ ‘, [rfReplaceAll]);
200 Глава 10. Для дома, для семьи
stroka:=CompressSpaces( stroka); end;
Поскольку процедура ShowSL для трансляции и работы требует в секции uses
указать модуль Dialogs, не включаю эту процедуру в свою микробиблиотеку, а привожу её здесь. Обратите внимание, что второй параметр, это параметр по умолчанию (default parameter).
procedure ShowSL( SL : TStringList; caption : string = ‘’); var stroka : string; i : integer;
begin
stroka:=caption + #13#10;
for i:=1 to SL.Count do begin stroka:=stroka + SL[i-1]; end;
ShowMessage(stroka); end;
В качестве заключительного штриха, для придания уверенности, неплохо
было бы проверить программу на всех представленных в предыдущем тексте примерах. Вы проверили? Нет? А ведь это даже не ваша программа, вам её не жалко. А мне жалко, но я проверил — программа работает. Наверное, случайно.
Написав программу, любую, или модуль, любой, обязательно подумайте —
как можно расширить его возможности. Для чего? Причин поступить так множе­ство. От вас могут попросить или потребовать реализовать новую возможность и будет лучше, если эта возможность уже реализована. Если вы попытаетесь рас­ширить функциональность, то, почти наверняка, какое-то место программы не выдержит и лопнет.
Сейчас у нас все варианты выбора лексемы имеют одинаковый шанс реализо-
ваться. Можно дать им разную вероятность, хотя стоит ли? Можно ввести пере­менные, задающие настройку на тему текста — пишем ли мы сейчас о политике, метафизике или о китайской эротике в трактовке Ван Гулика. И вообще, у меня много и других интересных идей.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]