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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
71Задача напоследок
function TArrayProcessing.GetArrPow : TArr; var i : integer;
begin
fArrPow:=Copy(fArr); for i:=Low(fArrPow) to High(fArrPow) do begin fArrPow[i]:=Power( fArrPow[i], fPow); end;
result:=fArrPow; end;
Сложнее она только своим первым оператором. Как я только что написал,
если присвоить один динамический массив другому, результат будет отличаться от того, который мы, скорее всего, изначально ожидали.
Задача напоследок
На входе объект — источник данных, на выходе объект приёмник данных. Для
разнообразия, в отличие от предыдущих примеров, которые имеют абсолютно практический характер, даже если вы в этом до сих пор сомневаетесь, этот при­мер будет красивый и бесполезный.
Мы напишем программу для проверки Гипотезы Гольдбаха. О чём идёт речь и
кто все эти люди? В 1742-м году мелкий немецкий математик Гольдбах отправил письмо великому русскому математику Эйлеру в исконно русский город Кали­нинград. В письме Гольдбах объявил, что каждое чётное число является суммой двух простых чисел. Изящно и красиво. А главное, всем понятно. Доказать Голь­дбах это не смог. И никто не смог. Гипотеза Гольдбаха до сих пор не доказана. Скорее всего потому, что за её доказательство не предложено ни копейки амери­канских денег.
// Уточнение
Проблема Гольдбаха является так называемой Шестой Проблемой Гильберта. Как говорится, это многое объясняет. То есть, если вы её докажете, вы станете знамени­тым в математических кругах. Но денег всё равно нидадуд.
// конец Уточнения
За теорией загляните в уже рекомендованную книгу — Д.Пойа «Математика и
правдоподобные рассуждения».
Если вы хотя бы на чуть-чуть математик, то прекрасно понимаете, что приве-
дение сколько угодно большого (но конечного) количества подтверждающих
72 Глава 3. С чего начинается работа
примеров любую гипотезу не доказывает, но и не опровергает. Обратное невер­но — для убиения гипотезы достаточно одного отрицательного примера.
// Совет
Посмотрите в интернетах, что такое теорема Гёделя о неполноте. Поразмышляйте, не подпадает ли под её действие Гипотеза Гольдбаха. Потом поразмышляйте, какое отношение вся эта фигня имеет к реальному миру, в котором мы живём,
// конец Совета
Так что мы, конечно, ничего не докажем и не опровергнем, но хотя бы раз-
влечёмся. Например, суммой каких двух простых чисел является 1000. Я, врать не буду, могу сосчитать в уме, и быстро, но из принципа не буду. Потому что мы сейчас напишем программу, немного искусственную. В программе будут два объекта, в смысле два экземпляра двух разных классов. Один класс будет порож­дать простые числа, другой будет записывать куда-то результаты их разложения в сумму. Посредине будет наша программа.
Поскольку мы всё равно не докажем и не опровергнем гипотезу Гольдбаха, то
давайте хотя бы проверим её до определённого предела, например для чисел до тысячи. Можно и больше, быстродействие современных компьютеров позволяет проверить её для чисел хотя бы и до миллиона за считанные секунды. Но не бу­дем увлекаться. Хватит и тысячи. Для целей самоограничения немедленно опре­деляем константу
const maxNumOfPrime = 1000;
А теперь первый класс, тот класс, который выдаёт нам простые числа, при-
мерно вот такой, в отношении интерфейса, включая секцию private, для удоб­ства дальнейшего изложения:
type
TPrimeIn = class public constructor Create; destructor Destroy; override;
function GetNext : integer; procedure Reset;
private Lin : TList; next : integer;
73Задача напоследок
procedure CalculatePrimes; end;
Комментарий. Что делают конструктор и деструктор, я пока не знаю. Но пусть
будут. Точнее, насчёт конструктора после размышлений я пришёл к выводу, что он создаст запас простых чисел, столько, сколько нам понадобится для провер­ки. Сколь бы презрительно я не относился к проблемам оптимизации, рассчиты­вать все простые числа до тысячи каждый раз, когда нам нужно всего одно про­стое число — это слишком.
Ещё один комментарий. Наш класс имеет имя TPrimeIn, второй класс будет
иметь имя TSumOut. С точки зрения самого класса, если, конечно, она, точка зрения, у него имеется, это нечестно и несправедливо. Потому что первый класс выдаёт простые числа наружу, во внешний мир (о котором он ничего не знает, это принципиально, класс не может знать, как будут использованы эти числа) а второй класс, напротив, принимает результаты на вход из внешнего мира. Но, с эгоистической точки зрения нашей программы (которой ещё нет), всё совсем наоборот. Для неё простые числа поступают на вход, а разбиение на суммы ухо­дят на выход. А поскольку все мы по природе своей немного эгоисты, именно в такой терминологии мы и будем рассуждать.
Как будет работать наша основная программа, я ещё не решил, но мне уже
кажется, для нашего первого класса, который генерирует простые числа, жела­тельна процедура, которая заставит его выдавать всю последовательность про­стых чисел с самого начала.
Покажем на примере. Мы запрашиваем пять простых чисел. Получаем
2,3,5,7,11. Мы запрашиваем ещё пять простых чисел. Получаем, естественно. 13,17,19,23,29. А если мы вызовем метод Reset, то снова получим 2,3,5,7,11.
Выходной класс, тот который TSumOut, будет по своим задачам прост и неза-
тейлив, что не означает, что он будет очень уж прост по своей реализации. Вся его задача в том, чтобы принять на вход разложение чётного числа на два про­стых, запомнить эту комбинацию и выдать при запросе. Если нам захочется со­хранить информацию для вечности в файл, то расширить функциональность класса будет несложно. А в начале он будет хранить запомненную информацию только для текущей сессии.
На вход этому классу будет подаваться исходное чётное число и два числа,
представляющее его разложение на сумму простых чисел. Хотелось бы этим ограничиться и сделать наш класс самодостаточным. Увы, для этого придётся выполнять грязную работу с указателями. Чтобы сократить возню с указателями до минимума, объявим вспомогательный, на один раз, тип.
type
TGoldbachRec = packed record number : integer; comp1 : integer;
74 Глава 3. С чего начинается работа
comp2 : integer; end;
Как легко догадаться, первое поле записи — это исходное чётное число, а два
следующих поля — сумма двух простых, на которые оно разлагается.
Класс для записи результатов очень прост:
type
TSumOut = class public constructor Create; destructor Destroy; override;
procedure Put( number : integer; comp1,comp2 : integer); function ToStr( ind : integer) : string; function ToStrAll : string; procedure Clear; procedure SaveToTextFile( fname : string);
private Lout : TList; end;
Всё ясно и понятно и очень похоже на симметричный ему класс TPrimeIn.
Разница в том, что у того класса был метод Reset, а у этого есть метод Clear, на­значение которых очень похоже.
Если в бассейн что-то втекает, то неизбежно должно и что-то вытекать, иначе,
зачем нам такой бассейн. Если в класс что-то входит, должен быть предусмотрен и обратный процесс. Иначе это называется чёрная дыра, а зачем нам чёрная дыра? Наш класс будет выдавать наружу данные в самом примитивном, тексто­вом виде. Примитивном не потому, что это самое простое для реализации, а по­тому, что с этими данными дальше нельзя сделать ничего, кроме как на них по­смотреть. Метод ToStr возвращает одну запись в текстовом формате, а метод ToStrAll выдаёт, как несложно догадаться, все записи.
Где нами будут храниться хранимые нами данные, предназначенные для хра-
нения? В списке, само собой. Список самый обычный, TList. Напоминаю, мы программируем на Delphi и используем стандартные классы. И, напоминаю, этот список хранит просто указатели. Ещё он легко и непринужденно может хра­нить любые совместимые с указателями по размеру (четырёхбайтовые) данные, но в данном конкретном случае нам это ни к чему. Список мы назовем Lout, и не забудем сослаться в секции uses на модуль Classes.
Остаётся ответить на простой и незатейливый вопрос — и в каком же месте мы
вкрутим собственно проверку гипотезы Гольдбаха? Сначала я думал, что это бу-
75Задача напоследок
дет какая-то промежуточная, самая главная, процедура. Потом я махнул рукой и вместо процедуры написал метод класса TPrimeIn, с вот таким интерфейсом:
function EvenToPrime( even : integer; var prime1,prime2 : integer) : boolean;
Технику программирования это заметно упрощает, все методы и переменные
класса здесь, под рукой. Объявим и вот такую константу, это максимальное ко­личество простых чисел, которые помнит наш класс, программа у нас всё-таки учебная, увлекаться не будем.
const maxNumOfPrime = 1024;
Реализация обоих классов проста и незатейлива, поэтому никаких предвари-
тельных обсуждений и рассказов, как мы дошли до жизни такой, не будет, только готовый текст. Вот это первый:
constructor TPrimeIn.Create; begin
Lin:=TList.Create; CalculatePrimes; next:=0; end; {----------------------------------------------------------}
destructor TPrimeIn.Destroy; begin
Lin.Free; end; {----------------------------------------------------------} procedure TPrimeIn.CalculatePrimes; var number : integer; isPrime : boolean; i : integer;
begin
Lin.Clear;
number:=2; Lin.Add(Pointer(number));
while (number < maxNumOfPrime) do begin number:=number + 1; isPrime:=true;
76 Глава 3. С чего начинается работа
for i:=2 to Ceil(Sqrt(number)) do begin if (number mod i) = 0 then begin isPrime:=false; Break; end; end;
if isPrime then Lin.Add((Pointer(number))); end; end; {----------------------------------------------------------}
function TPrimeIn.GetPrime( ind : integer) : integer; begin
if (ind >= 1) and (ind < Lin.Count) then begin result:=Integer( Lin[ind-1]); end else result:=0; end; {----------------------------------------------------------} function TPrimeIn.IsPrime( number : integer) : boolean; var i : integer;
begin
result:=false;
if ((number >= Integer(Lin[0])) and (number <= Integer(Lin[Lin.Count-1]))) then begin for i:=1 to Lin.Count do begin if number = Integer(Lin[i-1]) then begin result:=true; Break; end; end; end; end; {----------------------------------------------------------}
procedure TPrimeIn.Reset; begin
next:=0;
;
end
{----------------------------------------------------------} function TPrimeIn.EvenToPrime( even : integer; var prime1,prime2 : integer) : boolean; var i : integer;
begin
result:=false;
for i:=1 to (even div 2) do begin prime1:=GetPrime(i); prime2:=even - prime1; if isPrime(prime2) then begin result:=true; Break; end; end; end;
А вот второй:
constructor TSumOut.Create; begin
Lout:=TList.Create; end; {----------------------------------------------------}
destructor TSumOut.Destroy; begin
Clear; LOut.Free; end; {--------------------------------------------------------} procedure TSumOut.Put( number : integer; comp1,comp2 : integer); var p : pointer; rec : TGoldbachRec;
begin
rec.number:=number; rec.comp1:=comp1; rec.comp2:=comp2; GetMem( p, SizeOf(TGoldbachRec)); Move( rec, p^, SizeOf(rec)); Lout.Add(p); end; {--------------------------------------------------------} function TSumOut.ToStr( ind : integer) : string; var rec : TGoldbachRec;
begin
rec:=TGoldbachRec(Lout[ind-1]^); result:=IntToStr( rec.number) + ‘ = ‘ + IntToStr( rec.comp1) + ‘ + ‘ +
77Задача напоследок
78 Глава 3. С чего начинается работа
IntToStr( rec.comp2); end; {---------------------------------------------------} function TSumOut.ToStrAll : string; var i : integer;
begin
result:=’’;
for i:=1 to Lout.Count do begin result:=result + ToStr(i) + #13#10; end; end; {------------------------------------------------------} procedure TSumOut.Clear; var i : integer;
begin
for i:=Lout.Count downto 1 do begin FreeMem( Lout[i-1], SizeOf(TGoldBachRec)); Lout.Delete(i-1); end; end; {------------------------------------------------------} procedure TSumOut.SaveToTextFile( fname : string); var SL : TStringList;
begin
SL:=TStringList.Create; SL.Add(toStrAll); SL.SaveToFile(fname); SL.Free; end;
А теперь комментарии, как же без комментариев. Вечная тема — обратите внимание на метод Put. Мы там выделяем память
(GetMem присутствует), но не освобождаем (FreeMem отсутствует). Вам это должно быть понятно. Если непонятно, не паникуйте. Того, кому это понятно, я бы взял на работу без малейших размышлений. Того, кому это непонятно, я бы взял на работу под честное слово, что он это выучит. При этом половина сотруд­ников до сих пор в этом путается, так что, не переживайте.
#13#10 — Означает, что следующая порция вывода начнётся со следующей
строки. Ничего утончённо хакерского тут нет.
Метод ToStrAll сам, по сути, ничего не выводит, а обращается в цикле к мето-
ду ToStr. Так удобнее. На всякий случай.
А это основная программа:
PI:=TPrimeIn.Create; SO:=TSumOut.Create;
for i:=2 to 800 do begin even:=i*2; if PI.EvenToPrime( even, comp1,comp2) then begin SO.Put( even, comp1, comp2); end else ShowMessage(‘no hai’); end;
SO.SaveToTextFile(‘gold.txt’);
SO.Free; PI.Free;
Если формат вывода вас не удовлетворяет эстетически — ну, сделайте что-
нибудь, в конце концов!
79Задача напоследок
ГЛАВА 4.
Как продолжается работа.
Что-то ветвистое
Преамбула
В предыдущих разделах у нас получилось что-то длинное и прямое, наподобие
гадюки или ужа. А теперь займёмся чем-то круглым и ветвистым, наподобие па­учка или жабы.
Данные могут быть в большом количестве, но одного типа, даже если этот тип
сложный (в умеренных границах). В этом случае данные организуются в массив, одноуровневый список, или у нас есть какой-то виртуальный источник данных на входе, выдающий их по одному, и виртуальный приёмник данных на выходе, опять же принимающий их на вход тонкой струйкой. Именно это мы и наблюда­ли в нашем предыдущем примере, о проверке Гипотезы Гольдбаха.
Такие типы данных очень часты, именно в таком виде хранятся основные
данные, которые записывают, обрабатывают, выводят на экран, печатают, и что угодно ещё. Можно сказать, что это данные. Но, кроме данных, есть нечто, что можно назвать словом метаданные. Это может быть чем угодно, но метаданными я (по своему личному произволу) назвал их потому, что очень часто их назначе­нием является описание того, как понимать, как интерпретировать, как обраба­тывать и куда послать дальше наши основные данные.
Непонятно? Простой пример,.
Мы записали данные. Пусть они хранятся в одномерном массиве, или в фай-
ле, что почти одно и тоже. Это какие-то измерения — температура за окном в зависимости от дня недели, ваш вес в зависимости от номера недели в году, или альбедо планеты Юпитер в зависимости от Знака Зодиака. Данные мы знаем, но, кроме данных, мы знаем, что данные наши не совсем честные и точные. В случае градусника за окном и напольных весов на полу никто беспокоиться не будет. Но бывают и другие случаи.
Во всех случаях измеряющий прибор врёт. Он просто не может не врать. От-
сюда возникает вопрос — как из искажённых, испорченных — то есть измерен­ных данных получить не то, чтобы истинные, но хотя бы сколько-то к истинным приближенные? Возвращаемся к напольным весам.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]