Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Полезное программирование
.pdf
201Искатель тайного смысла. Обсуждение
Искатель тайного смысла. Обсуждение
Это хорошая, годная, бесполезная программа. Идите в Гугль и погуглите по
словам “Bible Code”.
Почему я не предлагаю поискать русские слова «Библейский код»? Потому
что первая же ссылка будет на русскую Википедию, в который вы найдёте вот
такую ахинею:
Библейский код (ивр.
тельность букв, якобы существующая в тексте Библии, которая при декодировании
показывает предвидения и пророчества.
Вы что-то поняли? Понять можно одно — в тексте Библии есть какой-то се-
кретный код, и этот секретный код содержит много секретного и очень интересного. Английская Википедия в этом отношении значительно более информативна и вразумительна. То есть в русской Википедии примерно три бессмысленных
строки, а в английской несколько осмысленных страниц. Так же неплохо заглянуть сюда http://skepdic.com/bibcode.html.
Теперь подробнее, с цитатами и примерами и предложением купить книгу.
Вот реклама. Кстати, и недорого, и непосредственно от авторов, от библейских
первоотцов, в смысле:
Bible Codes 2000
Based on the algorithms developed by pioneering code researcher Yoav Rosenberg
(of WRR), this Israeli software gives you the ability to search for codes confidently
throughout the Tanakh. Bible Codes 2000 generates reports on finds that include skips,
books, chapters, verses and letter locations, as well as letter numbers. Includes Hebrew
font. Creates matrices. Dictionary, concordance, name databases, more. Basic system
requirements. Runs on PCs with Windows 95, through Vista and Windows 7. $49.95 plus
$10 priority shipping direct from Israel.
Это только один экземпляр, таких программ море.
Теперь посмотрим на примере, пример, кажется, из американской Википе-
дии. Что мы здесь имеем? А имеем мы текст Библии, на английском, естественно, языке. Текст расположен по горизонтали. В каждой следующей строке текст
сдвигается на четыре символа налево. При этом четыре символа слева исчезают,
а четыре справа появляются. По вертикали мы пытаемся увидеть какие-то слова,
которых в тексте Библии нет. То, что мы увидели, выделено полужирным шрифтом. Увидели мы два слова. Roswell — деревушка в США, где потерпела катастрофу летающая тарелка с пупырчатыми человечками из другой галактики. Это
было в 1947-м году. UFO — то же самое, что по-русскм НЛО (неопознанный летающий объект). Обратите внимание, что UFO мы находим не просто по вертикали, а по вертикали через два символа. Мне кажется, что это нечестно, мы так
искать не будем.
) , также известный, как код Торы — последова-

202 Глава 10. Для дома, для семьи
S A N D M Y D A U G H T E R S T H O U H
M Y D A U G H T E R S T H O U H A S T N
U G H T E R S T H O U H A S T N O W D O
E R S T H O U H A S T N O W D O N E F O
H O U H A S T N O W D O N E F O O L I S
A S T N O W D O N E F O O L I S H L Y I
O W D O N E F O O L I S H L Y I N S O D
N E F O O L I S H L Y I N S O D O I N G
O L I S H K Y I N S O D O I N G I T I S
H L Y I N S O D O I N G I T I S I N T H
А сейчас ещё раз, но другими словами.
В чём состоит процедура с технической, и даже физической, точки зрения?
Список (не в виде книги, а в виде длинного рулона) Библии, Ветхого завета,
Торы, или Капитала Карла Маркса, наматывается на деревянный цилиндр. Наматывается аккуратно, и так, чтобы буквы в строках оказывались строго вертикально друг над другом. Затем мы ищем по вертикали интересующее нас слово,
например, «Вася». Откуда в Библии Вася? В Библии нет Васи, И даже в Капитале
нет Васи, хотя я могу и ошибаться. Но тех слов, что мы ищем по вертикали, и находим, в исходном тексте и нет. Эти слова искусственным и случайным образом
составились из букв слов, принадлежащих к разным строкам исходного текста.
Задача упрощается (только для евреев) тем, что в древнееврейском (не путать
с нынешним ивритом) больших и маленьких букв не было. Да и гласных не было.
Поэтому искать скрытый смысл было гораздо легче.
Пока я всё это писал, в новостях обнаружилось вот такое, обратите внимание
на цену:
Президент США Барак Обама скрывает от американцев библейское пророче-
ство, согласно которому Россия нападет на США в январе 2017 года, считает бывший кандидат в президенты от Республиканской партии Герман Кейн
В сообщении уточняется, что Россия использует против США электромагнит-
ную бомбу. Однако американцы смогут спастись, если купят за 49 долларов программу «Пережить конец времен».
Теперь постараюсь продемонстрировать на примере. Программу мы ещё не
написали, поэтому придется работать головой. Какой текст взять для опытов? Да
какая разница! Но, разумеется. текст должен быть оригинальным, то есть непереводным. Ход моей мысли очевиден — мы должны постичь бездны и глубины,
таящиеся в подсознании автора, а не како-то жалкого и ничтожного переводчика.
Берём вместо Библии роман в стихах «Евгений Онегин». Тренироваться будем
на нём. Он длинный и разнообразный.

203Искатель тайного смысла. Постановка задачи
Искатель тайного смысла. Постановка задачи
Задача наша такая — тест располагается в прямоугольную матрицу, то есть вот
так:
ɦɨɣɞɹɞɹɫɚɦɵɯɱɟɫɬɧɵɯɩɪɚɜɢɥɤɨɝɞɚ
ɧɟɜɲɭɬɤɭɡɚɧɟɦɨɝɨɧɭɜɚɠɚɚɬɶɫɟɛɹɡ
ɚɫɬɚɜɢɥɢɥɭɱɲɟɜɵɞɭɦɚɬɶɧɟɦɨɝɟɝɨɩ
ɪɢɦɟɪɞɪɭɝɢɦɧɚɭɤɚɧɨɛɨɠɟɦɨɣɤɚɤɚɹ
ɫɤɭɤɚɫɢɞɟɬɶɫɛɨɥɶɧɵɦɢɞɟɧɶɢɧɨɱɶɧ
ɟɨɬɯɨɞɹɧɢɲɚɝɭɩɪɨɱɶ
Обратите внимание, я изменил шрифт на моноширинный. Он не такой кра-
сивый, но идеально соответствует нашей задаче — подумайте, почему. И ещё,
было очень сложно набирать этот текст без пробелов. Это первая часть программы, число символов в строке задаётся извне, само собой.
Теперь мы задаём слово, по которому хотим найти откровение в тексте — «Ле-
нин», «Вова», «Вася», «Димон», «масоны», «баракобамы». Слова автоматически
приводятся к нижнему регистру, как и сам исходный текст.
Далее в действие вступает главный механизм поиска — он пробегает по всем
столбцам, и в каждом столбце, начиная с каждой строки сверху ищет заказанное
слово. Если находит, то сообщает об этом и выдаёт контекст, в котором слово
было найдено — например, все строки, которые пересекаются с этим словом, ну
и ещё по одной строке сверху и снизу. Это на тот случай, если предложение исходного текста оказалось разорванным пополам. Мне кажется, всё честно.
Я имею в виду, что на выходе мы получим абсолютно честное предсказание с
того света, в смысле — от умершего классика.
А теперь краткое содержание программ с разбиением на процедуры
1. Запросить имя исходного файла и прочитать его.
2. Убедиться, что файл более-менее текстовый, это не так просто как кажет-
ся, между прочим.
3. Прочитать весь файл и превратить его в таблицу, попутно выкинув пробе-
лы, переводы строк, и заменив большие буквы на маленькие.
4. Запросить ключевое слово для поиска и произвести собственно поиск.
5. Выдать результат.
Как видите, всё очень просто. Приступаем.
Искатель тайного смысла. Подготовка
Первое. Запросить имя текстового файла и прочитать его. Напрашивается
примерно вот такая организация программы

204 Глава 10. Для дома, для семьи
var
Text : TextFile;
begin
AssignFile( Text, fName);
Reset( Text);
// ɡɞɟɫɶɧɚɲɚɩɪɨɝɪɚɦɦɚɡɚɫɚɫɵɜɚɟɬɜɟɫɶɮɚɣɥɤɭɞɚɬɨ
CloseFile(Text);
end;
Хорошо ли это? Кажется, хорошо.
Но данные нам надо будет представить в виде прямоугольной таблицы, она же
двумерный массив, ведь работать мы будем с ним. Скорее всего, в каждой строке
у нас будет 32 символа — кажется, в этом был изначально некоторый сакральный
смысл, а кроме того число самым удачным образом представляет собой степень
двойки. Строк у нас для начала будет 16*1024. Почему именно столько? Потому
что максимальный размер стека, то есть суммарный объём всех доступных в процедуре переменных и всех параметров процедуры равен одному мегабайту. Его
можно легко увеличить, но нам, конечно же, лень Переменные можно объявить
глобально — но нам опять-таки лень, ведь мы пишем очень простую программу.
То есть, мы должны прочитать в цикле по строкам наш текстовый файл и не-
медленно обработать каждую строку — то есть переписать её в нашу таблицу.
Естественно, первая же прочитанная строка из файла в строку нашей таблицы
или не поместится полностью или будет длиннее, чем строка нашей таблицы.
Мы должны будем запомнить последний заполненный символ в строке нашей
таблицы, и номер строки, затем прочитать следующую строку из текстового файла, записать её с нужной позиции в строку таблицы и, если не влезло, перенести
невлезший остаток на следующую строку. А в чём проблема?
Да, собственно, проблемы нет. Но мозг человеческий настолько ограничен,
что заниматься хотя бы и всего двумя делами параллельно — читать файл и распихивать его по таблице — для этого ограниченного мозга слишком сложно. Для
моего мозга — точно. Я бы предпочёл сначала прочитать весь файл, а уже затем
заняться размещением его по таблице. Не надо параллельно, надо последовательно. Всё-таки, немного проще.
Но что это значит — прочитать весь файл? Прочитать его в какой-то массив?
Мы и так читаем его в какой-то массив. В чём разница? Образуется замкнутый
круг. Это я медленно подвожу вас к тому, что, как обычно, все проблемы уже возникали до нас. И всё уже запрограммировано до нас. Или почти всё.
Сюрприз — в Delphi есть вполне подходящий для наших целей класс
TStringList. Я о нём ещё не говорил? Если вы о нём не знали, или не знали, да ещё
и забыли, перечитайте справочную систему.
Следующий очень нехитрый код считывает весь наш текстовый файл — это
часть первая. Часть вторая, для демонстрации возможностей класса, выводит его
построчно на экран. Подумайте, хорошо ли это, если в тексте несколько тысяч
строк. Придите к выводу, что это плохо. Подумайте от том, как можно прервать

205Искатель тайного смысла. Подготовка
в любой момент это увлекательное занятие. Отвергните вариант перейти в среду
Delphi и нажать <CTRl/F2>. Придумайте что-то лучше.
var
SL : TStringList;
i : integer;
begin
GoHome;
SL:=TStringList.Create;
SL.LoadFromFile( ‘text.txt’);
for i:=1 to SL.Count do begin
ShowMessage(SL[i-1]);
end;
SL.Free;
end;
Итак, весь файл мы прочитали, причём добрый класс TStringList разобрал
весь текст по строкам и организовал из этих строк псевдомассив. Далее, лёгким
движением рук над клавиатурой, мы приводим все эти строки к нижнему регистру — если все строки и наше слово для поиска будут в одном и том же регистре,
то вероятность успешного результата значительно повышается. Попутно отсекаем начальные и конечные пробелы.
Достаточно ли этой подготовки текста? Увы, это зависит от самого текста.
Если затолкать внутрь что-то длинное и унылое (по форме), например «КонькаГорбунка», то всё будет неплохо. С «Евгением Онегиным» нас ждут проблемы —
роман разбит на строфы, между строфами пустые строки. Строфы пронумерованы римскими цифрами (на самом деле английскими буквами V, X и I). Само собой, беспощадному истреблению подлежат знаки препинания, цифры и…
Короче, убить всех, кто не русские буквы в нижнем регистре. Удобное их расположение в таблице кодировок значительно облегчает эту задачу.
В итоге, причёсывать файл будем вот так:
procedure BibPrepareText( SL : TStringList);
var
stroka : string;
i,k : integer;
begin
// ɩɟɪɟɜɟɫɬɢɜɧɢɠɧɢɣɪɟɝɢɫɬɪ
for i:=1 to SL.Count do begin
SL[i-1]:=AnsiLowerCase(SL[i-1]);
end;

206 Глава 10. Для дома, для семьи
// ɭɛɪɚɬɶɩɪɨɛɟɥɵɫɩɟɪɟɞɢɢɫɡɚɞɢ
for i:=1 to SL.Count do begin
SL[i-1]:=Trim(SL[i-1]);
end;
// ɭɛɪɚɬɶɩɪɨɛɟɥɵɜɨɨɛɳɟ
for i:=1 to SL.Count do begin
stroka:=SL[i-1];
for k:=Length(stroka) downto 1 do begin
if stroka[k] = ‘ ‘
then Delete( stroka, k, 1);
end;
SL[i-1]:=stroka;
end;
// ɭɛɪɚɬɶɜɨɨɛɳɟɜɫɺɱɬɨɧɟɦɚɥɟɧɶɤɢɟɛɭɤɜɵ
for i:=1 to SL.Count do begin
stroka:=SL[i-1];
for k:=Length(stroka) downto 1 do begin
if (Ord(stroka[k]) < 224)
then Delete( stroka, k, 1);
end;
SL[i-1]:=stroka;
end;
// ɭɛɪɚɬɶɩɭɫɬɵɟɫɬɪɨɤɢ
for i:=SL.Count downto 1 do begin
if SL[i-1] = ‘’ then SL.Delete(i-1);
end;
// ɭɛɪɚɬɶɫɬɪɨɤɢɞɥɢɧɚɤɨɬɨɪɵɯɦɟɧɶɲɟɞɟɫɹɬɢɫɢɦɜɨɥɨɜ
for i:=SL.Count downto 1 do
if Length( SL[i-1]) < 10 then SL.Delete(i-1);
end;
end;
begin
Убедитесь, что в секции uses прописан модуль SysUtils, обе функции,
AnsiLowerCase и Trim, содержатся в нём. Почему эти две операции не объединены в один цикл? Это мне подсказывает мой богатый жизненный опыт. Такой
вариант работает? Да. Такой вариант работает последовательно, разделяя две
операции на два прохода списка? Да. Значит он надёжнее. Хоть чуть-чуть, но надёжнее. Разумеется, для нашей несложной программы можно их и объединить,
никакого риска нет. Но если это реальная жизнь и это достаточно сложные операции, лучше пусть будет два прохода по данным, чем один, но двойной сложности. Да, накладные расходы возрастут, но важнее надёжность.

207Искатель тайного смысла. Подготовка
Почему тут вообще что-то повторяется, нельзя ли короче? Можно, но во-
первых, здесь отражён поток моих мыслей в процессе создания кода. Во-вторых
для наглядности. В третьих, для удобства внесения изменений и отката к предыдущей версии. Поясняю. Мне кажется, что удалять пустые строки надо безусловно. А вот удалять ли строки длиной меньше десяти — вопрос спорный, и особенно спорной является константа десять. Поэтому мне кажется нецелесообразным
объединять обе эти манипуляции в один цикл.
Следующим шагом предполагается преобразование нашего уже голого текста
в таблицу, то есть в двумерный массив символов. Делать это будем просто и без
затей — объявим обычный (не динамический) двумерный массив символов.
Массив по длине (строкам) достаточно длинный — чтоб влез весь Онегин, и не
слишком большой по ширине (столбцам). Предлагаю вот такой тип:
const
minY = 16;
maxY = 32;
maxX= 8*1024;
type
TChArr = array[1..maxX,1..maxY] of char;
Если вам этого мало, увеличьте, заодно увеличьте размер стека — Project\
Options\Linker (а вот когда я был маленький, у нас стек был… стыдно признаться,
в общем, тоже маленький).
Чтобы продать что-нибудь ненужное, нужно сначала купить что-нибудь
ненужное, а у нас денег нет © Трое из Простоквашино
Прежде чем что-то программировать, надо написать программу для тестиро-
вания этого чего-то. Но прежде чем написать программу для тестирования чегото, нам надо в точности знать, что именно мы тестируем. Разорвать это бесконечное коловращение сущностей позволяет примерно тот же механизм, который
разрывает цепь циклических ссылок. Сначала мы пишем интерфейс тестируемого модуля, затем программу для его тестирования, а потом и сам тестируемый
модуль. В нашем случае на вход у нас обязательно должно подаваться количество
символов в строке, а на выход — количество фактически заполненных строк.
Можно и не возвращать, определяя количество непробельных строк на лету, но
что-то мне подсказывает, что вариант с явным возвращением количества упростит дальнейшую жизнь.
procedure BibPrepareTable( SL : TStringList;
numY : integer;
var ca : TChArr;
var numX : integer);

208 Глава 10. Для дома, для семьи
Результат выполнения процедуры (массив ca) необходимо как-то просмо-
треть. Предпочтительнее, чтобы наша тестовая программа имела возможность
сохранить в текстовый файл двумерный массив с результатами, выводить его на
экран было бы чрезмерно утомительно. Для этого понадобится вспомогательная
процедура, далее текст. Обратите внимание, что внутри неё прячется старый знакомый — список строк. Создать объект, заполнить, сохранить в файл и уничтожить объект — проще, чем проделывать всё это руками с текстовым файлом
(TextFile).
procedure BibTableToFile( fName : string;
ca : TChArr;
numX : integer);
var
SL : TStringList;
i : integer;
begin
SL:=TStringList.Create;
for i:=1 to numX do begin
SL.Add( ca[i]);
end;
SL.SaveToFile(fName);
SL.Free;
end;
А вот так, по моему, должна выглядеть тестовая программа в целом:
var
SL : TStringList;
fName : string;
ca : TChArr;
numX : integer;
begin
SL:=TStringList.Create;
fName:=’eo.txt’;
if FileExists( fName) then begin
SL.LoadFromFile(fName);
BibPrepareText(SL);
BibPrepareTable( SL, 10, ca, numX);
SL.SaveToFile(fName + ‘bib’);
BibTableToFile( fName + ‘bibtab’, ca,numX);
ShowMessage( ‘Ok’#13#10 +
‘ numX = ‘ + IntToStr(numX));

209Искатель тайного смысла. Подготовка
end
else begin
ShowMessage( fName + ‘ not exists’);
end;
SL.Free;
end;
Минимальные комментарии в вопросах и ответах. Первый вопрос — в каком
каталоге эта программа будет искать текст Романа в Стихах? Ответ — а кто его
знает. Предположения есть, уверенности нет. Неплохо было бы перейти в начале
процедуры в тот каталог, в котором находится исполняемый файл программы,
или в тот, в котором, предположительно, должны находится наши текстовые
файлы с литературными источниками. Это актуально не только для этой программы, но и всегда, во все случаях. Другой вопрос — почему всё это наше богатство не оформлено в виде класса? Ответ — хотя я очень люблю ООП, но во всём
надо знать меру, здесь классы выглядят избыточной сущностью. Третий вопрос —
зачем задавать как параметр количество символов в строке? Не проще ли всегда
заполнять массив по всей ширине (maxY)? Потому что мы хотим немного сжульничать — если при канонической ширине строки в тридцать два символа мы ничего интересного не найдём, то переключимся на тридцать один символ, повторим поиск, и так далее, до двух символов в строке. Почему не до одного, понятно.
Теперь о теории, практике и методологии тестирования. В нашем тесте коли-
чество символов в строке задано равным десяти — потому что так легче считать и
проверять. Само собой неплохо бы проверить и на другое число — например три.
Но, абсолютно обязательной является и проверка на предельные случаи — только она показывает, как ни парадоксально это звучит, работоспособность нашей
функции в случаях стандартных.
Для начала стоит задать требуемое число символов в строке очень большим и
посмотреть, что получится. Сто символов подойдёт. Я попробовал. Катастрофы
не случилось, что меня, по некоторому размышлению, удивило. Я вспомнил, что
ширина массива совсем не 100, а всего 32, поэтому желательна проверка на максимально допустимое значение и принудительная замена на него. Разумеется,
если бы мы писали эту программу на продажу, этого было бы недостаточно. Потребовалась бы хоть какая-то, но диагностика, или в виде явного кода возврата —
нулевого или нет, или в виде возврата фактического количества символов в строке и его унылой проверки впоследствии.
Следующим номером нашей программы должна быть установка переменной в
минимально осмысленное значение — единицу. А значения меньше, опять-таки,
должны беспощадно отсекаться. Убедившись, что подготовительный процесс работает, приступаем к самой увлекательной части — поиску тайного смысла.

210 Глава 10. Для дома, для семьи
Искатель тайного смысла.
Реализация в первом приближении
Как всегда — сначала определить интерфейс, потом написать тестовую про-
грамму, потом напихать в интерфейс начинку. В качестве кандидата предлагается вот такое спорное решение:
type
TResArr = array[1..maxNum,1..2] of integer;
procedure BibSearch( ca : TChArr;
numX,numY : integer;
word : string;
var numOf : integer;
var rs : TResArr);
Что здесь вызывает сомнения? Хорошо ли это — задавать поиск только по од-
ному слову, или лучше было бы сразу заменить этот параметр на динамический
массив:
words : array of string;
Может быть, и лучше, для использования этой процедуры, в плане эффектив-
ности и скорости. Но, во-первых мы ещё не знаем, какими будут затраты времени на работу процедуры. Во-вторых, и отлаживать вариант с динамическим массивом будет сложнее.
А как мы, собственно, возвращаем результат? Не очень хорошим способом,
наши выходные параметры неочевидны. Хороший параметр в объяснениях не
нуждается. Мне же придётся пояснить, что numOf — число найденных вхождений слова, из чего следует, что мы не будем останавливаться, найдя слово в первый раз, но будем упорно продолжать поиск до самого конца текста. Что хуже,
мне пришлось объявить специальный, хотя и примитивный, тип для хранения
собственно результатов. По длине массива (первому индексу), понятно, будут записаны сами результаты (тавтология), а вот по ширине — строка, в которой начинается слово, и столбец. Два напрашивающихся вопроса — нельзя ли заменить
массив на одномерный и хранить в нём записи? И нельзя ли заменить массив на
список? Ответ — можно, и даже, наверное, нужно, но мы ведь пишем очень про-
стую, чисто для себя и для развлечения программу.
Ещё вопрос по интерфейсу — в классической технологии обнаружения би-
блейского кода ищется не просто слово, которого нет в оригинальном тексте, но
которое образуется в нём путём складывания текста в свиток. Этого мало. Найдя
заданное для поиска слово вася мы немедленно в его окрестностях ищем лексему, к примеру, нехороший человек или что-нибудь посильнее. Поскольку мы не
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
