Урок 3. Генератор писем

— А кто вам говорит, что вы должны сочинять какие-то фразы?

 

Отвлечемся на время от программирования и вспомним мировую классику.

Стендаль. «Красное и черное». Главный герой Жюльен общается со своим другом, русским князем Коразовым. Князь интересуется у Жюльена причинами его подавленного состояния. Жюльен рассказывает, что влюбился в госпожу де Дюбуа. Что та три дня страстно любила его, а затем прогнала. И что теперь он убит этим. Что предлагает князь? Простую схему: влюбить в себя какую-нибудь светскую красавицу и сделать так, чтобы госпожа де Дюбуа узнала об этом. То есть сыграть на чувстве ревности. При этом Жюльен должен соблюдать несколько простых правил: относиться к госпоже де Дюбуа так, как будто никакого разрыва не было; к светской красавице, которую он возьмется окручивать, не проявлять никаких чувств на публике, а всю свою страсть к ней выражать в письмах. Таким образом, госпожа де Дюбуа увидела бы востребованность Жюльена у противоположного пола, но при этом оставалась бы абсолютно уверенной в его чувствах к ней (именно поэтому Жюльен должен был разжигать страсть выбранной им светской красавицы через письма).

Так вот, катайте ей по два письма в день. — Ни за что, ни за что! — испуганно воскликнул Жюльен. — Пусть меня лучше живьем истолкут в ступе! Я не способен сочинить и двух фраз, я совершенный труп, дорогой мой, ничего от меня ждать нельзя. Бросьте меня, вот я здесь лягу и умру на краю дороги.

— А кто вам говорит, что вы должны сочинять какие-то фразы? У меня с собой в дорожной сумке лежит шесть томов любовных писем. Всех сортов, на любой женский характер. Найдутся и для образцовой добродетели! Ведь вот же Калисский волочился в Ричмонд-Террасе — это в трех лье от Лондона — за самой хорошенькой квакершей во всей Англии.

Когда в два часа ночи Жюльен расстался со своим другом, он чувствовал себя уже не столь несчастным.

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

Чем всё закончилось? Схема князя сработала. Но были некоторые сбои: Жюльен переписывал письма не очень внимательно и местами забывал заменить имя возлюбленной Калисского (автора писем) на имя светской красавицы, которую Жюльен хотел влюбить в себя.

А если бы у него был компьютер и письма были в электронном виде? Можно ли было избежать подобных ошибок и каким образом?

Давайте промоделируем эту ситуацию.

Конечно, мы будем моделировать её очень условно. Текст нашего письма будет выводиться на экран, а не в файл, который можно было бы переслать по электронной почте. Но сейчас это неважно, т. к. для нас главное — понять ряд принципов.

Итак, смотрим:


program letter;

uses
   Crt;

begin
   ClrScr;

   Writeln( 'Здравствуй, Милен!' );
   Writeln( '...' );
   Writeln( 'Засыпаю с твоим именем на устах — Милен, моя Милен' );
end.

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

Сохраните программу (F2 — File | Save) под именем letter.pas, а затем запустите её (Ctrl + F9) и посмотрите результаты работы программы в соответствующем окне (Alt + F5).

 

Что мы видим в этой программе? Программа очищает экран (с помощью процедуры ClrScr) и выводит на экран текст письма. В письме 3 раза встречается имя «Милен».

Допустим, нам нужно сформировать новое письмо, заменив имя «Милен» на «Элен». Как это сделать? С такой программой как выше у нас есть только один способ: пройтись по всему письму и заменить везде вручную имя «Милен» на «Элен». При этом мы рискуем допустить такую же ошибку как Жюльен, проморгав где-нибудь имя «Милен» и оставив его без изменений.

На самом деле у нас есть еще один способ: воспользоваться такой возможностью редактора Free Pascal как «Заменить...» (через меню Search | Replace...). Но об этой возможности я расскажу в конце урока.

А сейчас мы изменим нашу программу так, чтобы замену имени в письме можно было производить «в одно касание», не прибегая к возможностям редактора Free Pascal, и уж тем более, не заменяя имя вручную по всему письму.

Смотрим новый код:


program letterName;

uses
   Crt;

const
   Name = 'Милен';

begin
   ClrScr;

   Write( 'Здравствуй, ' );
   Write( Name );
   Writeln( '!' );

   Writeln( '...' );

   Write( 'Засыпаю с твоим именем на устах — ' );
   Write( Name );
   Write( ', моя ' );
   Writeln( Name );
end.

 

Сохраните новую программу под именем letterName.pas и запустите её (Ctrl + F9). Нажмите Alt + F5, чтобы посмотреть результат её работы.

 

Нажмите любую клавишу, чтобы вернуться в редактор. Найдите строку «const Name = 'Милен'» и замените имя «Милен» на «Элен».

Запустите программу снова (Ctrl + F9) и убедитесь, что на экран выводится теперь новое письмо (Alt + F5):

 

Проанализируем новый код. У нас появилась новая строка между «uses Crt;» и «begin»:

const
   Name = 'Милен';

«const» означает «константа». Константа — это постоянная величина, то, что не меняется. В этой строке мы «говорим» компилятору, что в нашей программе мы будем использовать константу с названием «Name», и здесь же указываем её значение: «'Милен'».

Здесь у вас может возникнуть вопрос: почему же Name — константа, если мы потом меняем её значение с 'Милен' на 'Элен'? Да, мы меняем значение Name, но мы делаем это до запуска программы и меняем его мы (!), а не сама программа. Когда мы объявляем Name как константу, то мы тем самым запрещаем изменять её значение программе (!). Конечно, можно продолжить и дальше задавать вопросы: например, как программа может сама что-то менять, если программу пишем мы, а значит, заранее знаем, что она будет делать, и если мы не хотим, чтобы какие-то значения менялись, то просто должны не менять их, т. е. не писать соответствующий код в своей программе? Это верный вопрос, но он выходит за рамки нашего текущего урока, и я останавлюсь на нём позже, когда мы начнем проходить переменные.

 

После того, как мы объявили константу Name, мы можем использовать её в своей программе везде вместо строки 'Милен'.

Вернемся на секунду к нашей первой программе в этом уроке (файлу letter.pas), а именно к строчке:

Writeln( 'Здравствуй, Милен!' );

Можем ли мы заменить эту строчку на такую?

Writeln( 'Здравствуй, Name!' );

Заменить-то мы можем, только результат на экране будет совсем не такой, как нам нужен:

Здравствуй, Name!

 

Запомните: всё, что находится между одинарными кавычками выводится на экран как обычный текст. Для компилятора 'Name' — это текст!

 

Если же мы напишем:

Writeln( Name );

то получим на экране:

Милен

 

Видите разницу между этими двумя командами: Writeln( 'Name' ) и Writeln( Name )?

В первом случае мы получаем на экране текст «Name».

Во втором — компилятор ищет идентификатор Name, и в нашем случае находит его в разделе констант. Далее компилятор берет значение ('Милен'), которое мы указали для константы Name, и даёт его процедуре Writeln. В результате процедура Writeln выводит на экран имя «Милен».

 

Чтобы вы наглядно представили себе эту разницу, я приведу такую аналогию. Представьте себе набор ящиков для хранения (например, как в супермаркете, куда вы складываете свои вещи, прежде чем зайти в торговый зал). И представьте себе, что у вас есть полоска бумаги, на которой написано Name. Так вот, полоска бумаги с надписью Name в нашем примере — это строка 'Name'. А тогда, что такое константа Name в нашей аналогии? А это ящик, на котором написано Name, и в котором лежит полоска бумаги с надписью 'Милен', выведенной ручкой, а не карандашом (то есть изменить надпись после запуска программы уже нельзя).

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

 

Снова возвращаемся к строке:

Writeln( 'Здравствуй, Милен!' );

После того, как мы ввели константу Name, мы переписали эту строку в программе letterName в следующем виде:

Write( 'Здравствуй, ' );
Write( Name );
Writeln( '!' );

В чем разница между Writeln и Write? После вывода текста на экран процедура Writeln переводит курсор на следующую строку (помните, ту строку, которая line по-английски?). «ln» в конце «Writeln» — это сокращение от слова line. Процедура же Write выводит текст на экран, оставляя курсор в той же строке, за последним напечатанным символом.

Смотрите как отличается вывод двух команд:

Write( 'Здравствуй, ' );

Здравствуй,  _

 

Writeln( 'Здравствуй, ' );

Здравствуй, 

_

 

Знаком «_» я показываю положение курсора.

Остальные строки программы вы можете проанализировать сами.

 

О процедурах вообще и Write/Writeln в частности

Мы с вами еще на первом уроке познакомились с процедурой Writeln. Что такое процедура? В самом начале, чтобы вам было легче усвоить понятие «процедура», я использовал его параллельно с синонимом «команда». В реальности, конечно, всё немного сложнее. Как правило, в обычной жизни мы понимаем под командами какие-то простые, понятные действия. Наиболее уместной тут будет аналогия с армией. Какие бывают команды? — Равняйсь, смирно, вольно, шагом марш, налево и т. д. В то же время, если мы возьмем какой-нибудь ракетный полк и рассмотрим команду «поразить цель такую-то», то за этой командой уже будет идти целая последовательность как простых, так и сложных действий. Например, выйти на заданную позицию, перевести ракеты в боевое положение, задать координаты цели и т. д. и т. п. То есть команду «поразить заданную цель» уже можно рассматривать как некую процедуру.

Еще обратите внимание, что у процедуры могут быть входные параметры. Что будет входными параметрами для процедуры поражения цели? Например, координаты цели и количество ракет, которые должны одновременно ударить по цели.

Возвращаясь к программированию, процедура может и не иметь входных параметров. Например, процедура ClrScr, которая очищает экран, не требует никаких данных. Или процедура Writeln, которая допускает отсутствие входных параметров. Если процедуре Writeln вы не передадите какой-либо текст, то тогда всё, что она сделает — это переведет курсор на следующую строку экрана (об этом упоминалось в разделе «Задания повышенной сложности» урока 2).

В этом плане процедуры Write/Writeln несколько отличаются от типовых процедур. Как правило, процедуры имеют фиксированное число параметров. Write/Writeln же могут принимать произвольное число параметров.

Вернемся к нашей программе letterName, а именно к строкам:

Write( 'Здравствуй, ' );
Write( Name );
Writeln( '!' );

Благодаря тому, что Write/Writeln могут принимать произвольное число входных параметров, приведенные строки можно сократить до одной строки:

Writeln( 'Здравствуй, ', Name, '!' );

 

Чтобы вы не запутались в запятых, я покажу как правильно набирать эту строку по шагам:

Writeln();

Writeln( '', Name, '' );

Writeln( 'Здравствуй, ', Name, '!' );

Посмотрите внимательно на вторую строку: мы передаем процедуре Writeln три входных параметра: строку, константу (являющуюся тоже строкой), и снова строку. Все параметры перечисляются через запятую. То есть: параметр, запятая, параметр, запятая, параметр (дальше запятая уже не нужна, т. к. идет закрывающая скобка). 

 

Перепишем программу letterName с учетом того, что мы теперь знаем о процедуре Writeln.

 

О возможностях редактора Free Pascal — Search | Replace...

Выше я обещал вам рассказать, как можно быстро произвести замену какого-либо слова в своей программе на любое другое с помощью редактора Free Pascal.

Вернемся к исходной программе letter.


program letter;

uses
   Crt;

begin
   ClrScr;

   Writeln( 'Здравствуй, Милен!' );
   Writeln( '...' );
   Writeln( 'Засыпаю с твоим именем на устах — Милен, моя Милен' );
end.

Чтобы заменить «Милен» на «Элен» с помощью редактора, нажмите Ctrl-Q A (или войдите в меню Search | Replace...).

Здесь нужно пояснить как нажать Ctrl-Q A: нажмите сначала Ctrl + Q, а потом (отпустив клавиши Ctrl и Q) нажмите клавишу А.

Появится окно Replace, в котором вы должны заполнить следующие поля:

Text to find (текст, который требуется найти) = Милен

New text (новый текст, тот, на который вы хотите заменить) = Элен

Убедитесь, что Scope (область поиска) = Global (по всему тексту), а Origin (с какого места искать) = Entire scope (по всей области поиска).

И на всякий случай установите галочку Prompt on replace (запрашивать подтверждение для каждой замены).

Нажмите «Change all» (заменить все найденные слова).

Каждый раз находя искомое слово (которое мы указали в поле Text to find), редактор будет спрашивать подтверждения на замену (помните, мы устанавливали галочку Prompt on replace?):

Нажимайте «Yes» («Да»), если вас устраивает предлагаемая замена.

Иногда бывает важно, чтобы редактор отличал регистр букв в слове, то есть, чтобы слова «Милен» и «милен» считались разными. Для этого при замене нужно устанавливать галочку Case sensitive (чувствителен к регистру) — см. окно «Replace» на предыдущем снимке экрана.

И чтобы больше не возвращаться к этому окну:

  • Whole words only означает, что заменять нужно только целые слова (представьте, что у вас в тексте есть слово «Миленькая», при замене «Милен» на «Элен» без установленной галочки Whole words only, ваша «Миленькая» превратится в «Эленькая»).
  • Scope = Selected text означает, что замену нужно производить только в выделенном фрагменте текста, а не во всей программе.

 

Немного о генерации писем

При покупке авиабилетов, бронировании гостиниц, или во время обычных покупок в интернет-магазинах, вас часто просят зарегистрироваться на сайте: указать свои фамилию, имя, адрес электронной почты. Впоследствии это позволяет авиакомпаниям, отелям, магазинам (и т. д.) присылать вам адресные письма. То есть открывая это письмо, вы увидите в нем не безличное «Уважаемый клиент» или «покупатель», а, например, «Дмитрий, ещё пара дней, и скидки на перелёты по промокоду time2fly закончатся...». Такие письма (адресные) больше располагают к себе, чем безличные.

Прочитав этот урок, вы уже понимаете, что генерацией этих писем занимаются специальные программы. Есть шаблоны писем и база данных, в которой хранятся данные о клиентах авиакомпании/магазина/и т. д. Программа извлекает из базы данных фамилию, имя клиента и подставляет их в нужное место в шаблоне письма. Конечно, подобные программы гораздо сложнее, чем тот пример, что мы разобрали выше, но принцип остается тот же: строка текста хранится в программе не в готовом виде, а формируется «на лету» на основе входных данных. Да, в нашей программе входные данные (имя возлюбленной) прописывались жестко в самой программе в виде константы, а не «приходили» извне (из базы данных, например), но сути это не меняет.

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

 

Краткое содержание урока

✔  Мы узнали как заменять текст в программе с помощью редактора Free Pascal:

  • Ctrl-Q A (Search | Replace...)

✔  Узнали новое ключевое слово Паскаля:

  • const — ключевое слово, после которого объявляются все константы, используемые в программе.

✔  Познакомились с еще одной процедурой:

  • Write — выводит на экран текст, оставляя курсор в той же строке (в отличие от Writeln, который переводит курсор на новую строку).

✔  Выяснили, что процедуры Write/Writeln могут принимать произвольное число входных параметров.

✔  Узнали, что такое генерация текста и зачем она может быть нужна.

 

Задания

  1. Напишите программу, которая выводит на экран текст для клиента авиакомпании S7:

Дмитрий,
Ещё пара дней, и скидки на перелёты по промокоду time2fly закончатся. Успейте воспользоваться! Если вы ничего не успели спланировать — не беда, мы подобрали отличные первомайские маршруты.
Указывайте ваш номер S7 Priority 804804804 при покупке билета или регистрации на рейс — теперь за каждый новый полёт рейсами S7 Airlines вы получите 500 или более миль.

Подумайте, что должно быть вынесено в отдельные константы.

Подсказка: даже если у вас в программе две и более констант, слово const используется один раз, например:

const
   FirstName = 'Иван';
   LastName  = 'Мельников';
   Age       = '35 лет';

При этом каждая новая константа идет отдельной строкой и заканчивается обязательно точкой с запятой. Еще обратите внимание, что значения констант выравнены по левому краю (выравнены знаки «=»). Это улучшает читаемость кода. Сравните:

const
   FirstName = 'Иван';
   LastName = 'Мельников';
   Age = '35 лет';