Биоритмы
Давно известно, что творческая и физическая активность человека не остается постоянной, циклически меняется, причем периодичность ее изменения приблизительно согласуется с периодом вращения Луны вокруг Земли. Существует теория, согласно которой физическая, эмоциональная и интеллектуальная активность человека подчиняется соответствующим биоритмам. Каждый биоритм представляет собой синусоиду со строго постоянным периодом, причем для каждого биоритма существует свой период. В отдельные дни все три биоритма человека могут достигнуть своего максимума и тогда человек испытывает подъем творческих и физических сил, в такие дни у него все спорится, он легко решает проблемы, которые в другое время ему решить гораздо сложнее. Точно также существуют и «черные» дни, соответствующие спаду всех трех биоритмов.
Используя уже опробованную методику нисходящего программирования, создадим программу, в которой запрашивается дата рождения человека и дата, для которой требуется оценить его состояние. Программа должна рассчитать и выдать на экран ближайшие к этой дате дни пика и спада биоритмов.
Алгоритм программы можно укрупнено записать следующим образом:
Будем считать, что каждое из перечисленных действий реализуется в отдельной процедуре, тогда начальный вариант программы будет таким:
Procedure InputDates(var dO,mO,yO,d,m,y: Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения) }
begin {InputDates}
end; {InputDates}
{..........................}
Procedure Get_count_pf_days (dO,mO,yO,d,m,y: Integer;
var days: Integer);
{Определяет полное количество дней, прошедших от одной даты до другой}
begin {Get_count_of_days}
end; {Get_count_of_days}
{--------------------------}
Procedure FindMaxMin (var dmin,dmax: Integer; days: Integer);
{Ищет критические дни}
begin {FindMaxMin}
end; {FindMaxMin}
{--------------------------}
Procedure WriteDates ( dmin , dmax , days : Integer);
{Определяет критические даты по количеству дней, прошедших от
момента рождения, и выдает эти даты на экран}
begin {WriteDates}
end; {WriteDates}
{--------------------------}
var
d0,d , {Дни рождения и текущий}
m0,m, {Месяцы рождения и текущий}
у0,у, {Годы рождения и текущий}
dmin, {Наименее благоприятный день}
dmax, {Наиболее благоприятный день}
days: Integer; {Количество дней от рождения}
begin {Главная программа}
Input-Dates (d0,m0,y0,d,m,y) ;
Get_numbers_of_days (d0,m0,y0,d,m,y,days) ;
FindMaxMin (dmin, dmax, days) ;
WriteDates (dmin, dmax, days)
end .
Начинаем детализацию программы. Прежде всего подумаем, как по двум датам вычислить разделяющее их количество дней? Если вспомнить, что следует учитывать неодинаковое количество дней по месяцам года, а также 29 февраля для високосных лет, то ответ на этот вопрос окажется не таким уж простым. Предлагаемый алгоритм подсчета количества дней заключается в вычислении количества дней от даты рождения до конца месяца, а затем и года рождения, количества дней, от начала текущего года до текущего месяца и текущей даты, а также - в подсчете количества полных лет, разделяющих обе даты. Количество лет затем легко пересчитывается в количество дней с учетом длины года (365 дней для обычных и 366 дней для високосных лет). Это очень прямолинейный алгоритм, но, откровенно говоря, мне не пришло в голову ничего другого. Возможно, существует более изящный способ подсчета и Вы его знаете, тогда программная реализация будет другой.
Упростить алгоритм можно за счет создания и использования массива из 12 целых чисел, содержащего количества дней по месяцам невисокосного года, т.е. 31, 28, 31, 30 и т.д. Этот массив (назовем его SIZE_OF_MONTH - длина _месяца) можно использовать и для обратной задачи, т.е. для определения даты критических дней, а также для проверки правильности вводимых дат. Таким образом, массив SIZE__OF_MONTH будет использоваться сразу в трех процедурах. Сделаем его глобальным, для чего его описание поместим перед описанием процедур:
const
Size_of_Month: array - [1. .12] of Byte =
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
{--------------------------}
Procedure InputDates (var d0,m0,y0,d,m,y: Integer);
.........
Поскольку описание массива размещается до описания процедур, он становится доступным внутри каждой из процедур и служит для них глобальным. В отличие от этого все константы и переменные, объявляемые внутри некоторой процедуры, являются локальными и могут использоваться только в этой процедуре.
С учетом сказанного напишем следующий начальный вариант программной реализации процедуры INPUTDATES:
Procedure InputDates(var d0,m0,y0,d,m,y: Integer);
{Вводит дату рождения и текущую дату. Контролирует правиль-
ность дат и их непротиворечивость (текущая дата должна быть
позже даты рождения)}
var
correctly: Boolean; {Признак правильного ввода}
begin {InputDates}
repeat
{Вводим и контролируем дату рождения d0,m0,y0.}
{Вводим и контролируем текущую дату d,m,y.}
{Проверяем непротиворечивость дат:}
correctly := у > у0; if not correctly and (у = y0) then
begin
correctly := m > m0;
if not correctly and (m = m0) then
correctly := d>=d0
end
until correctly
end; {InputDates}
В этой процедуре дважды выполняется одно и то же алгоритмическое действие (ввод и контроль даты). Это действие можно вынести в отдельную внутреннюю процедуру с именем INPDATE, тогда получим следующий окончательный вариант:
Procedure InputDates(var d0,m0,y0,d,m,y : Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения)}
var
correctly: Boolean; {Признак правильного ввода}
{--------------------------}
Procedure InpDate (text: String; var d,m,y: Integer);
{Выводит приглашение TEXT, вводит дату в формате ДД ММ ГГГГ и
проверяет ее правильность}
const
YMIN = 1800; {Минимальный правильный год}
YМАХ = 2000; {Максимальный правильный год}
begin {InpDate}
repeat
Write (text);
ReadLn (d,m,y) ;
correctly := (y >= YMIN) and (Y <= YMAX) and (m >= 1)
and (m <= 12) and (d > 0) ; if correctly then
if (m = 2) and (d = 29) and (y mod 4=0) then
{Ничего не делать: это 29 февраля високосного года!}
else
correctly := d <= Size_of_Month[m] ;
if not correctly then
WriteLn (' Ошибка в дате!')
until correctly
end; {InpDate}
{--------------------------}
begin {InputDates}
repeat
InpDate (' .Введите дату рождения в формате ДД ММ ГГГГ:',d0,m0,y0) ;
InpDate (' Введите текущую дату: ',d,m,y);
{Проверяем непротиворечивость дат:}
correctly := у > у0; if not correctly and (y = y0) then
begin
correctly := m > m0;
if not correctly and (m = m0) then
correctly := d >= d0
end
until correctly
end; {InputDates}
В самом общем виде алгоритм подсчета количества дней, разделяющих две даты, описан выше. При его реализации следует учесть три возможных варианта:
С учетом этого составим начальный вариант программной реализации процедуры
GET_NUMBERS_OF_DAYS :
Procedure Get_numbers_of_days (d,m,y,d,m,y: Integer;
var days: Integer);
{Определение полного количества дней, прошедших от одной даты до другой }
{--------------------------}
Procedure Variant2;
{Подсчет количества дней в месяцах,разделяющих обе даты}
begin {Variant2}
end; {Variant2}
{--------------------------}
Procedure Variant3 ;
{Подсчет количества дней в месяцах и годах, разделяющих обе даты}
begin {Variant3}
end; {Variant3}
{--------------------------}
begin {Get_numbers_of_days}
if (y = y0) and (m = m0) then {Даты отличаются только днями: }
days := d - d0
else {Даты отличаются не только днями:}
begin
days := d + Size_of_Month [m0] - d0;
{Учитываем количество дней в текущем месяце и количество дней до конца месяца рождения}
if (y0 mod 4=0) and (m0 = 2) then
inc(days); {Учитываем високосный год}
if у = y0 then
Variant2 {Разница в месяцах одного и того же года}
else
Variant3 {Даты отличаются годами}
end
end; {Get_numbers_of_days}
В этом фрагменте используется способ связи вспомогательных процедур VARIANT2 и VARIANT3 с основной процедурой через глобальные переменные, которыми являются параметры обращения к основной процедуре. Вспомогательные процедуры удобнее всего реализовать на основе циклов WHILE:
Procedure Variant2 ;
{Подсчет количества дней в месяцах, разделяющих обе даты }
var
mm : Integer;
begin {Variant2}
mm : = m0 ;
while mm < m do
begin
days := days + Size_of_Month [mm] ;
if (mm = 2) and (y0 mod 4=0) then
inc (days) ;
inc (mm)
end
end; {Variant2}
{--------------------------}
Procedure Variant3;
{Подсчет количества дней в месяцах и годах, разделяющих обе даты }
var
mm/ УУ : Integer;
begin {Variant3}
mm : = m0 + 1 ;
while mm <= 12 do {Учитываем остаток года рождения:}
begin
days := days+Size_of_Month [mm] ;
if (mm = 2) and (y0 mod 4=0) then
inc (days) ;
inc (mm)
end ;
yy := y0 + 1;
while yy < у do {Прибавляем разницу лет:}
begin
days : = days + 365;
if yy mod 4=0 then
inc (days) ;
inc (yy)
end;
mm : = 1 ;
while mm < m do {Прибавляем начало текущего года:}
begin
days := days + Size_of_Month [mm] ;
if (y mod 4=0) and (mm = 2) then
inc (days) ;
inc (mm)
end
end; {Variant3}
В процедуре FINDMAXMIN осуществляется поиск критических дней, т.е. ближайших к текущей дате дней, для которых все три биоритма достигают своего максимума и минимума. Предполагается, что биоритмы изменяются по законам синуса от количества прожитых дней с периодами ТF, ТE и TI соответственно для физической, эмоциональной и интеллектуальной активности человека. В программе приняты следующие периоды (в днях):
Знакомство с языком Турбо Паскаля
TF= 23.6884
ТЕ= 28.4261
TI= 33.1638
Самый простой алгоритм поиска заключается в том, чтобы вычислить значения сумм всех трех синусоид для текущего дня и для каждого из последующих дней на некотором заранее обусловленном интервале, например, в пределах месяца. Сопоставив результаты расчетов для каждого дня, нетрудно определить критические дни:
Procedure FindMaxMin(var dmin,dmax: Integer; days: Integer);
{Поиск критических дней}
const
TF = 2*3.1416/23.6884;{Период физической активности}
ТЕ = 2*3.1416/28.4261;{Период эмоциональной активности}
TI = 2*3.1416/33.1638;{Период интеллектуальной активности}
INTERVAL =30; {Интервал прогноза}
var
min, {Накапливает минимум биоритмов}
max, {Накапливает максимум биоритмов}
x : Real; {Текущее значение биоритмов}
i : Integer;
begin {FindMaxMin}
max := sin(days*TF)+sin(days*TE)+sin(days*TI);
min := max; {Начальное значение минимума и максимума равно значению биоритмов для текущего дня}
dmin := days;
dmax := days;
for i := 0 to INTERVAL do
begin
x := sin((days+i)*TF) + sin((days+i)*TE) + sin((days+i)*TI);
if x > max then
begin
max : = x;
dmax : = days + i
end
else if x < min then
begin
min := x;
dmin := days + i
end
end;
end; {FindMaxMin}
При разработке алгоритма процедуры WRITEDATES, с помощью которой на экран выводится результат работы программы, учтем, что основные сложности будут связаны с определением новой даты по начальной дате и количеству прошедших дней. Этот насчет будет повторяться дважды - для даты пика и даты спада биоритмов, поэтому его следует вынести в отдельную процедуру WRITEDATES. Кроме того, вряд ли Вы откажетесь от возможности вывода на экран дополнительной информации о том, сколько полных дней, часов, минут и секунд разделяют дату рождения человека и текущую дату. Однако реализация этого вывода не столь проста, как это может показаться на первый взгляд. Дело в том, что диапазон возможных значений данных типа INTEGER составляет от -32768 до +32767. Средняя продолжительность жизни человека - около 70 лет, т.е. 25550 дней. Это значение еще можно представить в Переменной типа INTEGER, однако часы, минуты и тем более секунды средней продолжительности жизни далеко превышают этот диапазон. Чтобы получить вывод достоверных данных, необходимо расширить диапазон значений целых чисел. Для этого в Турбо Паскале предусмотрен специальный тип данных LONGINT («длинный» целый), имеющий диапазон значений от -2147483648 до +2147483647 (см. гл. 4). Поэтому в процедуре WRITEDATES следует предусмотреть вспомогательную переменную этого типа, присвоить ей значение переменной DAYS и уже затем использовать «длинную» переменную для вычисления (и вывода) часов, минут, секунд. В результате начальный вариант процедуры WRITEDATES может быть таким:
Procedure WriteDates (dmin,dmax,days : Integer);
{Определение и вывод дат критических дней. Вывод дополнительной информации о количестве прожитых дней, часов, минут и секунд }
{---------------------}
Procedure WriteDate (text : String; dd : Integer);
{Определение даты для дня DD от момента рождения. В глобальных переменных d, m и у имеется текущая дата, в переменной DAYS -количество дней, прошедших от момента рождения до текущей даты.Выводится сообщение TEXT и найденная дата в формате ДД-МЕС-ГГГГ}
begin {WriteDate}
end; {WriteDate}
{---------------------}
var
LongDays: Longlnt; {"Длинная" целая переменная для часов,минут и секунд }
begin {Wri teDates}
LongDays : = days ;
WriteLn( 'Прошло: ', LongDays,' дней, ' , longDays*24, ' часов, ', LongDays*24*60, ' минут, ', LongDays*24*60*60, ' секунд');
WriteDate ( 'Наименее благоприятный день: ', drain);
WriteDate ( 'Наиболее благоприятный день: ',dmax)
end; {WriteDates}
Реализация процедуры WRITEDATE не вызывает особых сложностей:
Procedure WriteDate (text: String; dd: Integer);
const
Names_of_Monthes : array [1..12] of String [3] =('янв','фев','мар','апр','мая', 'июн','июл','авг','сен','окт', 'ноя','дек');
var
d0,m0,y0,ddd : Integer;
begin {WriteDate}
d0 := d;
m0 := m;
y0 : = y;
ddd := days;
while ddd<>dd do begin
inc(d0); {Наращиваем число}
if (y0 mod 4 <> 0) and (d0 > Size_of_Month[m0]) or (y0 mod ,4=0) and (d0=30) then
begin {Корректируем месяц}
d0 := 1;
inc(m0);
if m0 = 13 then {Корректируем год}
begin
m0 := 1;
inc(y0)
end
end;
inc(ddd)
end;
WriteLn(text,d0,'-',Names_of_Monthes[m0] ,'-',y0)
end; {WriteDate}
Собрав воедино отдельные части, получим полный текст программы (прил.5.2), предназначенной для определения биоритмов.