11
\$\begingroup\$

How many of you that still use your own knuckle to determine whether a month is having a full 31 days or less?

You job is to write a program to count how many months, in a month range, are having a full 31 days and how many are having less than 31 days by "counting the knuckles".

Counting days of month by knuckles

Courtesy: amsi.org.au


Input

A pair of months, the first of which doesn't have to come chronologically before the second, given in any suitable format. For instance: 201703 201902 — March 2017 to February 2019. Please describe the input format you choose. Note that the input must be able to include all years from 1 to 9999. The range of months specified includes both the starting and ending months.

Output

Two integers: the number of months in the given range with 31 days and the number of months in the range with less than 31 days.

Example: 14 10 — 14 knuckles, 10 grooves (it means that in that month range we have 14 months that have a full 31 days, and 10 months that have less than 31 days).

For an input where the second month in the range comes chronologically before the first, for example 201612 201611, you have to output a pair of zero.

Examples of input and output

| Input         | Output      |
|---------------|-------------|
| 201703 201902 | 14 10       |
| 201701 202008 | 26 18       |
| 000101 999912 | 69993 49995 |
| 201802 201803 | 1 1         |
| 201601 201601 | 1 0         |
| 201612 201611 | 0 0         |

Rules

  • You may choose any language you like
  • One input per line
  • This is , so shortest code in bytes wins!
  • The winner will be chosen in April 9
  • Standard loopholes apply
  • PS: this is my first question in PCG, it might have some inconsistencies. Feel free to edit and confirm what's unclear for you.
\$\endgroup\$
  • 5
    \$\begingroup\$ You say the first of which is guaranteed to come chronologically before the second, but this is false for test case 201612 201611. \$\endgroup\$ – Dennis Mar 30 '17 at 7:23
  • 2
    \$\begingroup\$ There exists a sandbox on the site where you can post the question solely to get all the doubts cleared. \$\endgroup\$ – ghosts_in_the_code Mar 30 '17 at 8:05
  • 1
    \$\begingroup\$ Thirty days hath September, April, June, and November. After February's done, all the rest have thirty-one. That's how I remember it. \$\endgroup\$ – AdmBorkBork Mar 30 '17 at 12:33
  • \$\begingroup\$ @AdmBorkBork The only trouble with that is the infinite looping (mandatory xkcd reference). \$\endgroup\$ – wizzwizz4 Mar 30 '17 at 18:19
  • \$\begingroup\$ I presume you intend to use the Gregorian calendar throughout the date range? \$\endgroup\$ – Paused until further notice. Mar 30 '17 at 19:57

11 Answers 11

7
\$\begingroup\$

Jelly, 21 bytes

ḅ12r/ị7RḂṁ12¤żC$S×⁼Ṣ$

Takes input like [[y, m], [y, m]].

Try it online!

How it works

ḅ12r/ị7RḂṁ12¤żC$S×⁼Ṣ$  Main link. Argument: [[a, b], [c, d]]

ḅ12                    Unbase 12; yield [x, y] := [ 12a + b, 12c + d].
   r/                  Reduce by range; yield [x, ..., y].
           ¤           Combine the five links to the left into a niladic chain.
      7                  Set the return value to 7.
       R                 Range; yield [1, 2, 3, 4, 5, 6, 7].
        Ḃ                Bit; yield [1, 0, 1, 0, 1, 0, 1].
         ṁ12             Mold 12; repeat the Booleans to create an array of length
                         12. Yields [1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1].
     ị                 At-index; yield the elements of the array to the right at 
                       the indices (1-based and modular) of the array to the left.
               $       Combine the two links to the left into a monadic chain.
              C          Complement; map t -> 1-t over the array.
             ż           Zip the original array with the complements.
                S      Take the sum of each column.
                    $  Combine the two links to the left into a monadic chain.
                  Ṣ      Sort [[a, b], [c, d]].
                   ⁼     Compare the result with [[a, b], [c, d]], yielding 1 if
                         the input is sorted, 0 if not.
                 ×     Multiply the results to both sides.
\$\endgroup\$
5
\$\begingroup\$

JavaScript (ES6), 70 68 67 64 bytes

Takes input as two integers in yyyymm format, in currying syntax (a)(b). Outputs an array of two integers [knuckles, grooves].

a=>g=(b,c=d=0)=>a>b?[c,d-c]:g(--b,c+!((b%=100)>11||b/.87&!!++d))

Formatted and commented

a =>                        // main function: takes start date (a) as input / returns g
  g = (                     // recursive function g, which takes:
        b,                  //   - b = end date
        c = d = 0           //   - c = number of knuckles
      ) =>                  // and also keeps track of: d = total number of months
    a > b ?                 // if a is greater than b:
      [ c, d - c ]          //   stop recursion and return the final result
    :                       // else:
      g(                    //   do a recursive call to g():
        --b,                //   - decrement the end date
        c +                 //   - increment the # of knuckles if
        !(                  //     both of these conditions are false:
          (b %= 100)        //     - the end month (now stored in b in 0-based indexing)
          > 11 ||           //       is greater than 11
          b / 0.87 & !!++d  //     - the number of days in this month is not 31
        )                   //       (at the same time, d is incremented if the first
      )                     //       condition is false)

Test cases

NB: The third test case is not included in this snippet, because it won't work unless your browser has Tail Call Optimization enabled.

let f =

a=>g=(b,c=d=0)=>a>b?[c,d-c]:g(--b,c+!((b%=100)>11||b/.87&!!++d))

console.log(f(201703)(201902)); // 14 10      
console.log(f(201701)(202008)); // 26 18      
console.log(f(201802)(201803)); // 1 1        
console.log(f(201601)(201601)); // 1 0        
console.log(f(201612)(201611)); // 0 0        

\$\endgroup\$
5
\$\begingroup\$

Python 2, 92 90 86 80 bytes

lambda a,b,c,d:[(bin(2741)[2:]*(c+1-a))[b-1:-12+d or None].count(x)for x in'10']

Try it online!

6 more by converting to a lambda, with thanks to @math_junkie for the idea. Now outputs a list containing the two numbers.

Previous non-lambda version (86 bytes)

a,b,c,d=input()
for x in'10':print(bin(2741)[2:]*(c+1-a))[b-1:-12+d or None].count(x),

Try it online old!

2 saved with thanks to @ovs for helping me get rid of the len(k). I hadn't thought about using None.

Input is a list of integers in the format y1,m1,y2,m2

Some credit due to @KeerthanaPrabhakaran who got bin(2741)[2:] before I did which saves 1 byte over hard coding the binary string.

\$\endgroup\$
  • \$\begingroup\$ Impressive... k=bin(2741)[2:]*(c+1-a) blew my mind \$\endgroup\$ – officialaimm Mar 30 '17 at 11:11
  • 1
    \$\begingroup\$ This is 2 bytes shorter \$\endgroup\$ – ovs Mar 30 '17 at 14:13
  • \$\begingroup\$ Brilliant! I was trying to work out hoe to get rid of the len(k) from the last slice. Thanks. \$\endgroup\$ – ElPedro Mar 30 '17 at 14:16
  • 1
    \$\begingroup\$ You can save 6 bytes by using a lambda: TIO \$\endgroup\$ – math junkie Mar 30 '17 at 15:17
4
\$\begingroup\$

PHP, 259 256 249 248 237 221 bytes

Outgolfed by aross: https://codegolf.stackexchange.com/a/114512/38505

Input Format: yyyymm,yyyymm

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";foreach(new DatePeriod($_(Ym,$i[0]),new DateInterval(P1M),$_(Ym,$i[1])) as$f)date(t,mktime(0,0,0,$f->format(m),1,$f->format(y)))>30?++$x:++$y;
echo $x.' '.++$y;

Try it online!


Older versions

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";$y=1;foreach(new DatePeriod($_("Ym",$i[0]),new DateInterval('P1M'),$_("Ym",$i[1])) as$f)date("t",mktime(0,0,0,$f->format("m"),1,$f->format("y")))==31?++$x:++$y;
echo $x.' '.$y;

Try it online!

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";$y=1;foreach(new DatePeriod($_(Ym,$i[0]),DateInterval::createFromDateString('1 month'),$_(Ym,$i[1])) as$f)date(t,mktime(0,0,0,$f->format(m),1,$f->format(y)))>30?++$x:++$y;
echo $x.' '.$y;

Try it online!

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";$y=1;foreach(new DatePeriod($_(Ym,$i[0]),DateInterval::createFromDateString('1 month'),$_(Ym,$i[1])) as$f)date(t,mktime(0,0,0,$f->format(m),1,$f->format(y)))==31?++$x:++$y;
echo $x.' '.$y;

Try it online!

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";foreach(new DatePeriod($_("Ym",$i[0]),DateInterval::createFromDateString('1 month'),$_("Ym",$i[1])) as$f)date("t",mktime(0,0,0,$f->format("m"),1,$f->format("y")))==31?++$x:++$y;
echo $x.' '.++$y;

Try it online!

$i=explode(",",fgets(STDIN));$_="DateTime::createFromFormat";$y=1;foreach(new DatePeriod($_("Ym",$i[0]),DateInterval::createFromDateString('1 month'),$_("Ym",$i[1])) as$f)date("t",mktime(0,0,0,$f->format("m"),1,$f->format("y")))==31?++$x:++$y;
echo $x.' '.$y;

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Batch, 93 bytes

@set/ag=(y=%2/100-%1/100)*5+(x=%2%%100+6)*5/12-(w=%1%%100+5)*5/12,k=y*12+x-w-g
@echo %k% %g%

Accepts two parameters in ymm format (i.e. 101 - 999912). Previous 129-byte loop-based solution:

@set/al=%1-%1/100*88,u=%2-%2/100*88,k=g=0
@for /l %%i in (%l%,1,%u%)do @set/a"m=%%i%%12,k+=1451>>m&1,g+=2644>>m&1
@echo %k% %g%
\$\endgroup\$
  • \$\begingroup\$ Incorrect result for 000101 999912, probably because of the integer size limit? \$\endgroup\$ – officialaimm Mar 30 '17 at 9:41
  • 1
    \$\begingroup\$ @officialaimm Wrong input format, sorry - year should not have leading zeros. \$\endgroup\$ – Neil Mar 30 '17 at 10:18
3
\$\begingroup\$

Python 3.5 (164 162 154 152 150 148 140 137 bytes)

n=int;a,b=input().split();t=k=0
for r in range(n(a[4:]),(n(b[:4])-n(a[:4]))*12+n(b[4:])+1):t+=1;k+=n('101010110101'[r%12-1])
print(k,t-k)

repl.it

takes input in the form of yyyymm yyyymm

prints output as number_of_knuckles number_of_grooves

  • saved 2 bytes: Thanks to Cole
  • saved 8 bytes: removed unwanted variables
  • saved 2 bytes: reduced t=0;k=0 as t=k=0
  • saved 2 bytes: Thanks to Cole (I had missed this before)
  • saved 2 bytes: Thanks to Keerthana
  • saved 8 bytes: removed unwanted variables
  • saved 3 bytes: Thanks to math_junkie ( split(' ') to split() )
\$\endgroup\$
  • 1
    \$\begingroup\$ I think you can reduce some bytes by doing n=int and also perhaps some exec tomfoolery. \$\endgroup\$ – cole Mar 30 '17 at 7:03
  • 1
    \$\begingroup\$ I think you can do 2773&1<<r%12-1>0 instead of int('101010110101'[r%12-1]) \$\endgroup\$ – Loovjo Mar 30 '17 at 7:53
  • \$\begingroup\$ @Loovjo I am getting error in doing so! \$\endgroup\$ – officialaimm Mar 30 '17 at 8:06
  • 1
    \$\begingroup\$ using print([k,t-k]) as print(k,t-k) would produce the desired result as (k,g) thereby reducing 2 bytes! \$\endgroup\$ – Keerthana Prabhakaran Mar 30 '17 at 9:12
  • 1
    \$\begingroup\$ I believe you can replace split(' ') with split() \$\endgroup\$ – math junkie Mar 30 '17 at 13:40
3
\$\begingroup\$

Python 2, 147 146 142 bytes

def s(a,b):y=100;r=bin(2741)[2:];x=b/y-a/y;i=r*(x-1);return((0,0),map((i+r[a%y-1:]+r[:b%y]if i or x else r[a%y-1:b%y]).count,('1','0')))[a<=b]

Try it online!

  • Saved 4 bytes - Thanks to @math_junkie for suggesting the if-else clause with array lookup!

Breaking down the code,

def s(a,b):
 y=100
 r=bin(2741)[2:] #'101010110101'
 x=b/y-a/y #to get the difference between the two years
 i=r*(x-1)
 return((0,0),map((i+r[a%y-1:]+r[:b%y]if i or x else r[a%y-1:b%y]).count,('1','0')))[a<=b]
\$\endgroup\$
  • 1
    \$\begingroup\$ You can save bytes by replacing the if-else clauses with array lookups. See this post for details \$\endgroup\$ – math junkie Mar 30 '17 at 15:07
  • \$\begingroup\$ That was indeed a cool way! Didn't know that before! Thanks! \$\endgroup\$ – Keerthana Prabhakaran Mar 30 '17 at 15:22
3
\$\begingroup\$

PHP, 120 103 97 96 bytes

for($f=strtotime;$f($argv[2])>=$n=$f($argv[1].+$x++.month);)$k+=date(t,$n)>30;echo+$k,_,$x-1-$k;

Run like this:

php -nr 'for($f=strtotime;$f($argv[2])>=$n=$f($argv[1].+$x++.month);)$k+=date(t,$n)>30;echo+$k,_,$x-1-$k;' 0001-01 9999-12;echo
> 69993_49995

Explanation

for(
  $f=strtotime;          # Alias strtotime function which is called twice.
  $f($argv[2]) >=        # Create end date timestamp. Iterate until the end
                         # date is reached.
  $n=$f(
    $argv[1].+$x++.month # Create timestamp from start date + X months.
  );
)
  $k+=date(t,$n) > 30;   # If "t" of current date (days in month) is 31
                         # increment $k (knuckles).

echo+$k,_,$x-1-$k;       # Compute grooves (iterations - $k) and output,
                         # implicit cast to int to account for 0 count.

Tweaks

  • Saved 17 bytes by using timestamp style instead of DateTime object style
  • Saved 6 bytes by not assigning end date timestamp to variable $e, just compare directly
  • Saved 1 byte by not keeping count of grooves, but just computing it after the loop
\$\endgroup\$
  • \$\begingroup\$ $x++ instead of +$x++ also works. \$\endgroup\$ – Titus Mar 31 '17 at 17:24
  • \$\begingroup\$ @Titus, I had that at first, but realized that with $x uninitialized the string would be 2017-12month, which is an unrecognized format and results in 1970 \$\endgroup\$ – aross Apr 3 '17 at 13:37
  • \$\begingroup\$ Wicked ... it worked somewhere. Wicked enough though that it works without a literal + in the string. \$\endgroup\$ – Titus Apr 4 '17 at 12:33
2
\$\begingroup\$

PowerShell, 96 bytes

for($a,$b=[datetime[]]$args;$a-le$b;$a=$a.AddMonths(1)){$x++;$z+=$a.Month-in2,4,6,9,11};$x-$z;$z

Try it online!

Takes input as form 2017-03. Uses the built-in .NET date libraries and loops through from inputs $a to $b, each iteration incrementing $x++ and adding to $z if the current .Month is -in 2,4,6,9,11 (i.e., a non-31-day month). Then we output our total months minus the non-31-day months $x-$z, and the non-31-day months $z.

Tosses an error on the 0001-01 to 9999-12 test case, because .NET only supports years up to 9999, so the final .AddMonths(1) causes an overflow. Still outputs the correct values, though, because it's a non-terminating error; it just causes the loop to exit.

Probably would be shorter to do this arithmetically, like the Python or JavaScript answers, but I wanted to show an approach using the .NET built-ins.

\$\endgroup\$
2
\$\begingroup\$

Bash, 113 bytes

s="$1-1";e="$2-1";sort <(while [ "$s" \< "$e" ];do s=$(date +%F -d"$s+1month");date +%d -d"$s-1day";done)|uniq -c

Try it online!

needs golfing...

takes input as 2016-03 2018-10

outputs:

  1 28
  7 30
 12 31

ungolfed:

s="$1-1"
e="$2-1"                     # adds first day of month to the dates
sort <(                    
while [ "$s" \< "$e" ]; do   #iterates over dates
s=$(date +%F -d"$s+1month")  #adds one month to start date
date +%d -d"$s-1day"         #outputs last day of previous month
done) | uniq -c              #counts ocurrences of day number prevously sorted
\$\endgroup\$
1
\$\begingroup\$

Swift, 151 bytes

let f={(m:[Int])->[Int] in var k=[0,0]
(m.min()!...m.max()!).map{$0%100}.filter{$0>0&&$0<13}.forEach{m in let n = m>7 ?m-7:m
k[(n%2+1)%2]+=1}
return k}

input is an array of two integers in the format as per the example

\$\endgroup\$

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.