Biorhythms

From Rosetta Code
Revision as of 16:18, 5 September 2020 by Markjreed (talk | contribs) (Create task.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Task
Biorhythms
You are encouraged to solve this task according to the task description, using any language you may know.

For a while in the late 70s, the pseudoscience of biorhythms was popular enough to rival astrology, with kiosks in malls that would give you your weekly printout. It was also a popular entry in "Things to Do with your Pocket Calculator" lists. You can read up on the history at Wikipedia, but the main takeaway is that unlike astrology, the math behind biorhythms is dead simple.

It's based on the number of days since your birth. The premise is that three cycles of unspecified provenance govern certain aspects of everyone's lives – specifically, how they're feeling physically, emotionally, and mentally. The best part is that not only do these cycles somehow have the same respective lengths for all humans of any age, gender, weight, genetic background, etc, but those lengths are an exact number of days! And the pattern is in each case a perfect sine curve. Absolutely miraculous!

To compute your biorhythmic profile for a given day, the first thing you need is the number of days between that day and your birth, so the answers in Days between dates are probably a good starting point. (Strictly speaking, the biorhythms start at 0 at the moment of your birth, so if you know time of day you can narrow things down further, but in general these operate at whole-day granularity.) Then take the residue of that day count modulo each of the the cycle lengths to calculate where the day falls on each of the three sinusoidal journeys. The three cycles and their lengths are as follows:

Cycle Length in Days
Physical 23
Emotional 28
Mental 33

The first half of each cycle is in "plus" territory, with a peak at the quarter-way point; the second half in "minus" territory, with a valley at the three-quarters mark. You can calculate a specific value between -1 and +1 for the _k_th day of an _n_-day cycle by computing sin( 2π_k_ / _n_ ). The days where a cycle crosses the axis in either direction are called "critical" days, although with a cycle value of 0 they're also said to be the most neutral, which seems contradictory.

The task: write a subroutine, function, or program that will, given a birthdate and a target date, output the three biorhythmic values for the day. You may optionally include a text description of the position and the trend (e.g. "up and rising", "peak", "up but falling", "critical", "down and falling", "valley", "down but rising"), an indication of the date on which the next notable event (peak, valley, or crossing) falls, or even a graph of the cycles around the target date. Demonstrate the functionality for dates of your choice.

Example run of my Raku implementation: <lang sh>raku br.raku 1943-03-09 1972-07-11</lang>

Output:
Day 10717:
Physical day 22: -27% (down but rising, next transition 1972-07-12)
Emotional day 21: valley
Mental day 25: valley

Double valley! This was apparently not a good day for Mr. Fischer to begin a chess tournament...

Raku

<lang raku>#!/usr/bin/env raku my %cycles = ( :23Physical, :28Emotional, :33Mental ); my @quadrants = [ ('up and rising', 'peak'),

                 ('up but falling',   'transition'),
                 ('down and falling', 'valley'),
                 ('down but rising',  'transition') ];

sub MAIN($birthday, $date = Date.today()) {

 if !$birthday {
   die "Birthday not specified.\n" ~
       "Supply --birthday option or set \$BIRTHDAY in environment.\n";
 }
 my ($bday, $target) = ($birthday, $date).map: { Date.new($_) };
 my $days = $target - $bday;
 say "Day $days:";
 for %cycles.sort(+*.value)».kv -> ($label, $length) {
   my $position = $days % $length;
   my $quadrant = floor($position / $length * 4);
   my $percentage = floor(sin($position / $length * 2 * π )*1000)/10;
   my $description;
   if $percentage > 95 {
     $description = 'peak';
   } elsif $percentage < -95 {
     $description = 'valley'; 
   } elsif abs($percentage) < 5 {
     $description = 'critical transition'
   } else {
     my $transition = $target + floor(($quadrant + 1)/4 * $length) - $position;
     my ($trend, $next) = @quadrants[$quadrant];
     $description = "$percentage% ($trend, next $next $transition)";
   }
   say "$label day $position: $description";
 }

}</lang>

Output:
$ br 1809-01-12 1863-11-19
Day 20034:
Physical day 1: 26.9% (up and rising, next peak 1863-11-23)
Emotional day 14: critical transition
Mental day 3: 54% (up and rising, next peak 1863-11-24)

Tcl

Works with: Wish

A graphing version using Tcl+Tk: <lang tcl>#!/usr/bin/env wish

  1. Biorhythm calculator

set today [clock format [clock seconds] -format %Y-%m-%d ] proc main [list birthday [list target $today]] {

 set day [days-between $birthday $target]
 array set cycles { 
   Physical  {23 red}
   Emotional {28 green}
   Mental    {33 blue}
 }
 set pi [expr atan2(0,-1)]
 canvas .c -width 306 -height 350 -bg black
 .c create rectangle 4 49 306 251 -outline grey
 .c create line 5 150 305 150 -fill grey
 .c create line 145 50 145 250 -fill cyan
 .c create text 145 15 -text "$target" -fill cyan 
 .c create text 145 30 -text "(Day $day)" -fill cyan 
 set ly 305
 foreach {name data} [array get cycles] {
   lassign $data length color
   .c create text 60 $ly -anchor nw -text $name -fill $color
   set pos [expr $day % $length]
   for {set dd -14} {$dd <= 16} {incr dd} {
     set d [expr $pos + $dd]
     set x [expr 145 + 10 * $dd]
     .c create line $x 145 $x 155 -fill grey
     set v [expr sin(2*$pi*$d/$length)]
     set y [expr 150 - 100 * $v]
     if {$dd == 0} {
       .c create text 10 $ly -anchor nw \
           -text "[format %+04.1f%% [expr $v * 100]]" -fill $color
     }
     if [info exists ox] {
       .c create line $ox $oy $x $y -fill $color
     }
     set ox $x
     set oy $y
   }
   unset ox oy
   set ly [expr $ly - 25]
 }
 pack .c

}

proc days-between {from to} {

 expr int([rd $to] - [rd $from])

}

  1. parse an (ISO-formatted) date into a day number

proc rd {date} {

 lassign [scan $date %d-%d-%d] year month day
 set elapsed [expr $year - 1]
 expr {$elapsed * 365 +
      floor($elapsed/4) -
      floor($elapsed/100) +
      floor($elapsed/400) +
      floor( (367*$month-362)/12 ) +
      ($month < 3 ? 0 : ([is-leap $year] ? -1 : -2)) +
      $day}

}

proc is-leap {year} {

 expr {$year % 4 == 0 && ($year % 100 || $year % 400 == 0)}

}

main {*}$argv</lang>

Output:

Output of <lang sh>wish br.wish 1809-02-12 1863-11-19</lang> - Lincoln's biorhythms at Gettysburg: https://i.imgur.com/U2izZOM.png