13
\$\begingroup\$

Write a program that takes a birth date (month and day-of-month) as input and outputs the corresponding sign, element and quality of the western zodiac. For the purposes of this challenge, these are defined exactly as in the table in the linked wikipedia page:

Sign           Date Range                    Element    Quality

Aries          March 21 to April 19          Fire       Cardinal
Taurus         April 20 to May 20            Earth      Fixed
Gemini         May 21 to June 21             Air        Mutable
Cancer         June 22 to July 22            Water      Cardinal
Leo            July 23 to August 22          Fire       Fixed
Virgo          August 23 to September 22     Earth      Mutable
Libra          September 23 to October 23    Air        Cardinal
Scorpio        October 24 to November 20     Water      Fixed
Sagittarius    November 21 to December 22    Fire       Mutable
Capricorn      December 23 to January 20     Earth      Cardinal
Aquarius       January 21 to February 21     Air        Fixed
Pisces         February 22 to March 20       Water      Mutable

Rules

  • The sign, element and quality will be calculated from the input date exactly according to the wikipedia table.
  • I am giving some freedom over input date format (see Input section below). You must clearly state in your answer what format you are using.
  • If an invalid date is provided (unparsable date, or month or day-of-month out of range), the program will exit with the message Invalid date.
  • The program must correctly handle leap years. i.e. if Feb 29th is the input, then output must correctly be Pisces, Water, Mutable.
  • Your language's Date libraries/APIs are allowed, but any APIs that specifically calculate signs of the zodiac are banned.
  • Standard “loopholes” which are no longer funny
  • This is , so the shortest answer in bytes wins.

Input

  • The program may read the input date from STDIN, command-line, environment variables or whatever method is convenient for your language of choice.
  • This is not primarily an exercise in datestring-parsing, so the input string may be provided in whatever format you choose, as long as it consists of only month and day-of-month components and not other values (such as year or time). E.g. Jul 24 or 24th of July or 7/24 or 24/07 or whatever format is convenient. If input not matching your choice of input format is entered, then the Invalid date error must be thrown.

Output

  • The program will output to STDOUT, dialog box or whatever display method is convenient for your language of choice.
  • The output format will be the sign, element and quality separated by a comma and a space: Sign, Element, Quality

Examples

Input                 Output
1/1                   Capricorn, Earth, Cardinal
2/29                  Pisces, Water, Mutable
7/24                  Leo, Fire, Fixed
13/1                  Invalid date
2/31                  Invalid date
-1/-1                 Invalid date
1st of Golfember      Invalid date
\$\endgroup\$
  • \$\begingroup\$ Is detection/warning of an invalid date required? If so, can we assume invalid numbers entered by the user will be greater than zero? (I'm guessing most people will use a numerical date format.) \$\endgroup\$ – Level River St Jul 24 '14 at 19:35
  • 1
    \$\begingroup\$ @steveverrill I think I made that clear in the third item of the rules, but I've edited the Input section to clarify. TLDR valid input => valid output; invalid input => error message; no "undefined behaviour" allowed for any given input. \$\endgroup\$ – Digital Trauma Jul 24 '14 at 19:46
  • \$\begingroup\$ is a full date allowed? days, months and years? \$\endgroup\$ – Teun Pronk Jul 25 '14 at 12:10
  • \$\begingroup\$ @TeunPronk No, just month and day-of-month. I've clarified the input rule. \$\endgroup\$ – Digital Trauma Jul 25 '14 at 14:32
  • \$\begingroup\$ Is the day of the year (e.g. Jan 21 -> 21, Feb 5 -> 36) a valid input format? \$\endgroup\$ – Mego Jun 9 '16 at 7:15
8
\$\begingroup\$

JavaScript, 285 bytes

(My first answer here after lurking here for a while)

d=prompt(m=prompt(e='MutableCardinalFixedAirWaterFireEarthCapricornAquariusPiscesAriesTaurusGeminiCancerLeoVirgoLibraScorpioSagittarius'.match(/[A-Z][a-z]+/g)));alert(m>0&m<13&d>0&d<29-~'202121221212'[--m]?e[n=7+m%12+(d>18-~'121012333413'[m])]+', '+e[n%4+3]+', '+e[n%3]:'Invalid date')

The first prompt() is the month in numerical form, and the second prompt() is the day of the month (Ignore the text of the prompt). Leading zeroes are optional for both. The output is displayed with alert(). (Thanks to bitpwner, Snack, edc65, and core1024 for helping shorten the code.)

Try it above using Stack Snippets or at http://jsfiddle.net/8vq89/5/.

Code with whitespace and comments to explain the confusing parts added:

var m = prompt(), d = prompt(),
e = 'MutableCardinalFixedAirWaterFireEarthCapricornAquariusPiscesAries\
TaurusGeminiCancerLeoVirgoLibraScorpioSagittarius'.match(/[A-Z][a-z]+/g);
alert(m > 0 && m < 13 && d > 0
      & d < 29 - ~'202121221212'[--m] /* ~ is bitwise NOT, which yields -(x + 1). It also
                                         converts a string to a number. Subtracting that
                                         number from 29 gives the number of days in the
                                         month given. */
      ? e[n = 7 + // 7 is added to skip over the 7 qualities and elements in array e.
      m % 12 // The modulus allows dates near the end of December to wrap around to January.
      + (d > 18 - ~'121012333413'[m])] + ', ' +
      e[n % 4 + 3] + /* Qualities and elements follow a pattern, so the modulus determines
                        which one it is. 3 is added to skip over the 3 qualities. */
      ', ' + e[n % 3] : 'Invalid date')
\$\endgroup\$
  • 1
    \$\begingroup\$ For 'Cardinal0Fixed0Mutable'.split(0) and 'Earth0Air0Water0Fire'.split(0), ['Cardinal','Fixed','Mutable'] and ['Earth','Air','Water','Fire'] is a little shorter. \$\endgroup\$ – Snack Jul 25 '14 at 8:58
  • \$\begingroup\$ I had a solution, but too similar: z='Air9Water9Fire9Earth9Fixed9Mutable9Cardinal9Aquarius9Pisces9Aries9Taurus9Gemini9Cancer9Leo9Virgo9Libra9Scorpio9Sagittarius9Capricorn'.split(9); [m,d]=prompt().split(/\D/); alert([m-->0&m<12&d>0&d<(32-'020101001010'[m])?z[(m+=(18-~'121012333413'[m]<d)+11)%12+7]+', '+z[m%4]+', '+z[m%3+4]:'Invalid date']). Take a look at the single split \$\endgroup\$ – edc65 Jul 25 '14 at 15:05
  • \$\begingroup\$ @edc65 I get an Invalid left-hand side in assignment error when I try to run that, probably having to do with the prompt \$\endgroup\$ – NinjaBearMonkey Jul 25 '14 at 16:04
  • \$\begingroup\$ group assignment is Ecmascript 6, just works in firefox. p=prompt().split(/\D/),m=p[0],d=p[1] should work (but so longer) \$\endgroup\$ – edc65 Jul 25 '14 at 16:12
  • 3
    \$\begingroup\$ You can make it even shorter if you use '...ScorpioSagittarius'.match(/[A-Z][a-z]+/g) instead of '...Scorpio0Sagittarius'.split(0) \$\endgroup\$ – core1024 Jul 31 '14 at 19:03
3
\$\begingroup\$

C 353 352

Edit Fixed bug and typo, 1 char more

Before you ask: yes, even without #include is valid and working C standard.
Input format: two numbers, first month, then day.

char*s[]={"Air","Water","Fire","Earth","Fixed","Mutable","Cardinal","Aquarius","Pisces","Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius","Capricorn"};
main(m,d){
  scanf("%d%d",&m,&d);
  m>0&m<13&d>0&d<(32-(4460832>>m>>m&3))
  ?m+=10+(" 121012333413"[m]-29<d),printf("%s, %s, %s\n",s[m%12+7],s[m%4],s[m%3+4])
  :puts("Invalid date");
}

Test

char*s[]={"Air","Water","Fire","Earth","Fixed","Mutable","Cardinal","Aquarius","Pisces","Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius","Capricorn"};
char *test(char *o, int m, int d)
{
  char *result=o;
  m>0&m<13&d>0&d<(32-(4460832>>m>>m&3))
  ?m+=10+(" 121012333413"[m]-29<d),sprintf(o, "%s, %s, %s\n",s[m%12+7],s[m%4],s[m%3+4])
  :(result = 0);
  return result;
}

int main()
{
  char buf[100];
  int m,d;
  for (m=0;m<14;m++)
    for(d=0;d<40;d++)
      if (test(buf, m,d)) printf("%d %d %s", m, d, buf);
  return 0;
}
\$\endgroup\$
  • \$\begingroup\$ Good, except for the typo of Ivalid ;-) \$\endgroup\$ – Digital Trauma Jul 26 '14 at 18:06
  • \$\begingroup\$ @DigitalTrauma Not so good, maybe it has a bug on discriminating sign start (21,20,24 etc). I'm revising it just now. Stay tuned \$\endgroup\$ – edc65 Jul 26 '14 at 18:18
2
\$\begingroup\$

Perl 287 (286 + 1 for the -p flag)

/\//;$_=32-(26830452>>$`*2&3)<$'|$'<1|$`<1|$`>12?'Invalid date
':qw(Aquarius01 Pisces23 Aries45 Taurus61 Gemini03 Cancer25 Leo41
Virgo63 Libra05 Scorpio21 Sagittarius43
Capricorn65)[$`-1-($'<20+(219503166088>>$`*3&7))].$/;s/\d/', '.qw(Air
Fixed Water Mutable Fire Cardinal Earth)[$&]/eg

The input is from STDIN and the output goes to STDOUT. Here are the tests from OP:

1/1
Capricorn, Earth, Cardinal
2/29
Pisces, Water, Mutable
7/24
Leo, Fire, Fixed
13/1
Invalid date
2/31
Invalid date
-1/-1
Invalid date
1st of Golfember
Invalid date
\$\endgroup\$
1
\$\begingroup\$

Python, 447 387 characters

import sys
try:S=sys.argv[1];a,b=int(S[:2])-1,int(S[3:])
except:a=b=0
print['Invalid date',', '.join((s.split()*5)[a+(b>[20,21,20,19,20,21,22,22,22,23,20,22][a%12])]for s in"Capricorn Aquarius Pisces Aries Taurus Gemini Cancer Leo Virgo Libra Scorpio Sagittarius Capricorn|Earth Air Water Fire|Cardinal Fixed Mutable".split("|"))][-1<a<12and 0<b<[32,30,32,31,32,31,32,32,31,32,31,32][a]]

Takes input as MM/DD, must always be two digits.

$ python zods.py 01/01
Capricorn, Earth, Cardinal
$ python zods.py 02/29
Pisces, Water, Mutable
$ python zods.py 07/24
Leo, Fire, Fixed
$ python zods.py 13/01
Invalid date
$ python zods.py 02/31
Invalid date
$ python zods.py -1/-1
Invalid date
$ python zods.py First of golfember
Invalid date
$ python zods.py
Invalid date
\$\endgroup\$
  • 1
    \$\begingroup\$ You can try these: [20,21,20,19,20,21,22,22,22,23,20,22][a%12] -> 19+int('121012333413'[a%12]) and [32,30,32,31,32,31,32,32,31,32,31,32][a] -> 30+int('202121221212'[a]) \$\endgroup\$ – Vectorized Jul 25 '14 at 0:52
1
\$\begingroup\$

Javascript, 403 396 bytes

M=prompt(),D=+prompt(),i=[52,85,117,147,180,213,246,278,310,343,372,406,415];if([0,0,-2,0,1,0,1,0,0,1,0,1,0][M]<D-31||M<1||D<1||M>12)alert("Invalid date");else for(x in i)if(M*32+D<=i[x]){alert("Capricorn0Aquarius0Pisces0Aries0Taurus0Gemini0Cancer0Leo0Virgo0Libra0Scorpio0Sagittarius0Capricorn".split(0)[x]+", "+["Earth","Air","Water","Fire"][x%4]+", "+["Cardinal","Fixed","Mutable"][x%3]);break}

Another approach. I think I can golf down more, but later.

Input month on first prompt, date on second prompt.

\$\endgroup\$
1
\$\begingroup\$

PHP - 294 bytes (w/o php tags*)

*As the other php entry did

Input:Month Day as integers, leading zero not required.

Ex:php starsign.php <<< "2 29" for February 29th.

Golfed (do not copy this directly, see below):

<?@eval(gzinflate('=Œ1Â0…ÿŠC!Ë
¦q+JQ—
BEq¸¶¡Ô¤^ðç{¤"ï>/Ãò®`³GOBqú7v¥±MP#äpúµ;$:Å€Ý$“g¦Þ³ƒê‘).p¦¥·TL’Œ,ÝѾȑ¬]oëáJ<zh¨c„V3yhq¤VË_ü(²®TZË¥ØêܳÓF_'));die(fscanf(STDIN,'%u%u',$m,$d)/2&$m<13&&$m*$d&&$d<33-$b[$m]?$a[7+$m-=$d<20+$b[$m+9]].$a[$m%4].$a[$m%3+4]:'Invalid date');

Hexdump of php file:

3f3c 6540 6176 286c 7a67 6e69 6c66 7461
2865 3d27 318c c20b 1030 ff85 438a cb21
a60d 2b71 4a1d 9751 420a 0745 b871 a1b6
d41e 5ea4 f013 7be7 22a4 ef07 1e3e 2f8f
f2c3 60ae 47b3 4f0e 4205 7115 37fa 760c
03a5 4db1 2350 e40f fa70 1fb5 243b c53a
dd80 9324 671a dea6 83b3 1dea 2991 702e
a5a6 0bb7 4c54 1792 2c8c d1dd c8be ac91
6f5d 1a19 e1eb 3c4a 687a 63a8 5684 3304
6879 a471 5610 5fcb 28fc aeb2 5a54 9d1b
a5cb ead8 18dc d3b3 1546 275f 2929 643b
6569 6628 6373 6e61 2866 5453 4944 2c4e
2527 2575 2775 242c 2c6d 6424 2f29 2632
6d24 313c 2633 2426 2a6d 6424 2626 6424
333c 2d33 6224 245b 5d6d 243f 5b61 2b37
6d24 3d2d 6424 323c 2b30 6224 245b 2b6d
5d39 2e5d 6124 245b 256d 5d34 242e 5b61
6d24 3325 342b 3a5d 4927 766e 6c61 6469
6420 7461 2765 3b29

To generate golfed file, run the following php script (generates to starsign.php):

<?php
$a=<<<'NOW'
$a=[', Earth',', Air',', Water',', Fire',', Cardinal',', Fixed',', Mutable',Capricorn,Aquarius,Pisces,Aries,Taurus,Gemini,Cancer,Leo,Virgo,Libra,Scorpio,Sagittarius,Capricorn];$b='1131212112121012333413';
NOW;
$p = '<?@eval(gzinflate(\'';
$s = <<<'NOW'
'));die(fscanf(STDIN,'%u%u',$m,$d)/2&$m<13&&$m*$d&&$d<33-$b[$m]?$a[7+$m-=$d<20+$b[$m+9]].$a[$m%4].$a[$m%3+4]:'Invalid date');
NOW;
file_put_contents('starsign.php',$p . gzdeflate($a) . $s);

Completely ungolfed version:

<?php
$a=[', Earth',', Air',', Water',', Fire',', Cardinal',', Fixed',', Mutable',Capricorn,Aquarius,Pisces,Aries,Taurus,Gemini,Cancer,Leo,Virgo,Libra,Scorpio,Sagittarius,Capricorn];$b='1131212112121012333413';
if(fscanf(STDIN, '%u%u', $m, $d) == 2
 and $m < 13
 and $m 
 and $d
 and $d < 33-$b[$m]){
    if($d < 20 + $b[$m+9])
        --$m;
    die($a[7+$m] . $a[$m%4] . $a[$m%3+4]);
}else die('Invalid date');
\$\endgroup\$
  • \$\begingroup\$ Finally figured out that I need to edit /etc/php5/cli/php.ini and set short_open_tag = On to get this to work. Works for this answer, but the other PHP answer still errors out. +1 for this one. \$\endgroup\$ – Digital Trauma Jul 28 '14 at 17:04
1
\$\begingroup\$

Python 3 - 332 bytes

s="Capricorn Aquarius Pisces Aries Taurus Gemini Cancer Leo Virgo Libra Scorpio Sagittarius Earth Air Water Fire Cardinal Fixed Mutable".split()
import sys
try:m,d=map(int,sys.argv[1:]);13>m>0<d<b" 313232332323"[m]-19or E
except:x="Invalid date"
else:i=m-1+(d>b" 121012333413"[m]-29);x=s[i%12]+", "+s[-7+i%4]+", "+s[-3+i%3]
print(x)
  • Takes input as separate arguments (i.e. 1 1)

  • One split for all strings

  • Indexing bytes in py3 gives the ord

  • or E is a cheap way to generate an exception (NameError)

  • Is there a cheaper way to concatenate the strings? + and literals turned out to be smaller than join.

  • There's no good reason for one bytes lookup to start with 1 and the other with 0, I just forgot the 29-day February and changed 0->1 instead of redoing the whole thing.

\$\endgroup\$
0
\$\begingroup\$

PHP 548 524 502 419 (w/o php tags)

Requires PHP >= 5.4.0

Input: Day Month (numerical value starting at 1) e.g. 2 3 for March 2nd.

<?
$x='Invalid date';
@list($d,$m)=split(' ',fgets(STDIN));
if(@$m<1||$m>12||@$d<1)die($x);
$z=['Aquarius','Pisces','Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Air','Water','Fire','Earth','Fixed','Mutable','Cardinal','121012333413202121221212'];
$m=$m+0;$d<$z[19][--$m]+20&&$m--;$m<0&&$m=11;$d>$z[19][$m+12]+29&&die($x);
echo$z[$m+0],", {$z[$m%4+12]}, {$z[$m%3+16]}";

EDIT: The A non well formed numeric value notice is caused by performing a pre-increment on the string $m. Regardless of the notice, the following output should still be correct. This notice varies per version of PHP. To fix this, a simple string to integer conversion should be placed before the second last line. I've updated the code with the conversions.

Sample Input/Output (on Windows):

// March 2nd
Input > echo 2 3 | php star.php
Output > Pisces, Water, Mutable

// Feb. 30th
Input > echo 30 2 | php star.php
Output > Invalid date

// June 30th
Input > echo 30 6 | php star.php
Output > Cancer, Water, Cardinal
\$\endgroup\$
  • \$\begingroup\$ OK, I'm a php dummy. How do you run this? I tried echo 2 3 | php -f zod.php, but it just echoes the script itself to STDOUT \$\endgroup\$ – Digital Trauma Jul 26 '14 at 18:12
  • 1
    \$\begingroup\$ @DigitalTrauma I think that you need to enable the short tags in your interpreter, otherwise use <?php instead of <?. \$\endgroup\$ – core1024 Jul 26 '14 at 18:24
  • \$\begingroup\$ I tried that, but now I get PHP Notice: A non well formed numeric value encountered in /home/ubuntu/zod.php on line 6. I'm running php 5.5.9 on Ubuntu 14.04 \$\endgroup\$ – Digital Trauma Jul 26 '14 at 19:13
  • \$\begingroup\$ Code fixed. See edit. \$\endgroup\$ – noahnu Jul 29 '14 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.