Биоритмы

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

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

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

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

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