Биоритмы

Давно известно, что творческая и физическая активность человека не остается постоянной, циклически меняется, причем периодичность ее изменения приблизительно согласуется с периодом вращения Луны вокруг Земли. Существует теория, согласно которой физическая, эмоциональная и интеллектуальная активность человека подчиняется соответствующим биоритмам. Каждый биоритм представляет собой синусоиду со строго постоянным периодом, причем для каждого биоритма существует свой период. В отдельные дни все три биоритма человека могут достигнуть своего максимума и тогда человек испытывает подъем творческих и физических сил, в такие дни у него все спорится, он легко решает проблемы, которые в другое время ему решить гораздо сложнее. Точно также существуют и «черные» дни, соответствующие спаду всех трех биоритмов.

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

Алгоритм программы можно укрупнено записать следующим образом:

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

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), предназначенной для определения биоритмов.