6
\$\begingroup\$

You'll get a human-readable question matching one of the following syntaxes, and are expected to print/return the corresponding result. Finding similarities between the human-readable inputs and matching them with minimum size code is explicitly part of the task, so I won't give a formal definition.

  • "Will October begin on a Thursday if this year is a leap year, this month is January and this month begins on a Wednesday?" > "Yes."
  • "If this month begins on a Friday, this month is October and this year is not a leap year, will December start on a Tuesday?" > "No, it will be a Wednesday."
  • "If this month is August, this month begins on a Wednesday and this year is not a leap year, will October start on a Sunday?" > "No, it will be a Monday."
  • "Will December start on a Monday if this month begins on a Friday, this year is a leap year and this month is November?" > "No, it will be a Sunday."

"Formal" definition:

Input will always be generated by picking one of the above lines and swapping day names, month names, and the "not " in and out of "is *a leap year"

Further notes:

  • Each question will consider only one calendar year, i.e. you won't be asked for a date of the following or previous calendar year.
  • This is codegolf, shortest answer in bytes in each language wins.
  • In case you need it, remember leaps years are not strictly every 4 years.
  • No answer will be accepted.
  • Standard loopholes apply.
  • You can input and output in any reasonable way.

Just for reference:

  • 'January', 'February','March','April', 'May','June','July','August', 'September','October','November','December'
  • 'Monday','Tuesday', 'Wednesday','Thursday', 'Friday','Saturday', 'Sunday'

Inspired by Squarish's Kata on codewars.com

\$\endgroup\$
  • 6
    \$\begingroup\$ As with pretty much all natural-language posts, and especially those involving parsing, this needs a formal definition, or else it risks being too broad \$\endgroup\$ – caird coinheringaahing Oct 29 at 14:12
  • 1
    \$\begingroup\$ So the "formal" definition really is a formal definition? \$\endgroup\$ – qwr Oct 30 at 6:03
  • \$\begingroup\$ @qwr: It formally wasn't \$\endgroup\$ – Zsolt Szilagy Oct 30 at 11:42
  • \$\begingroup\$ @ZsoltSzilagy I would prefer to call something like BNF "formal". \$\endgroup\$ – tsh Oct 31 at 2:19
10
\$\begingroup\$

JavaScript (ES6), 254 bytes

s=>(F=e=>'0x'+'0A39135?2B?8602467'[parseInt(s.match(e+' ([A-Z]\\w+)')[1],33)%234%81%19],n=(F`s.{5}`-(g=m=>new Date(~F`(no|le).*`,m).getDay())(F`s`)+g(F`l`)+7)%7)-F` .{9}a`?`No, it will be a ${'Sun,Mon,Tues,Wednes,Thurs,Fri,Satur'.split`,`[n]}day.`:`Yes.`

Try it online!

How?

Given the leading part e of a regular expression, the helper function F looks for the capitalized word that immediately follows and turns it into either a 0-indexed day (from 0 = Sunday to 6 = Saturday) or a 0-indexed month (from 0 = January to 11 = December).

F = e =>                // e = regular expression part, as a string
  '0x' +                // parse as hexadecimal
  '0A39135?2B?8602467'[ // a digit between '0' and 'B'
    parseInt(           //   selected by 1) parsing
      s.match(          //     the sub-string in s that matches
        e +             //       the leading part of the regular expression
        ' ' +           //       followed by a space
        '([A-Z]\\w+)'   //       followed by a capturing group for
      )[1],             //       a capitalized word (which is what we keep)
      33                //     in base 33
    )                   //   end of parseInt()
    % 234 % 81 % 19     //   and 2) applying a modulo chain
  ]                     // end of digit lookup

Try the hash function online!

We invoke F with:

  • "s.{5}" to match "begins on a [day]"
  • "s" to match "is [month]"
  • "l" to match "will [month]" (the w may also be capitalized)
  • " .{9}a" to match either " start on a [day]" or " begin on a [day]"
  • "(no|le).*" to match either "not" or "leap", whichever comes first (this is a special case where the capitalized word is put in a 2nd capturing group and ignored)

The helper function g takes a month and returns a week day in [0..6]:

g = m => new Date(~F`(no|le).*`, m).getDay()

The expression ~F`(no|le).*` in there evaluates to -1 if "no" is matched (not leap) or to -4 if "le" is matched (leap). This works as expected because the year -4 is leap1 and the year -1 is not.

Using g and the data collected above, we create two dates in the same reference year and look for the difference between the corresponding week days, modulo 7. This allows us to answer the question.


1: At least that's what JS thinks. But this is apparently not true.

| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$

Retina 0.8.2, 292 bytes

no.*
$&~
Feb(?=.*~)|Mar|N|Sa
4
F|Au
3
Ma|Th
2
O|We
1
A|Jul|T
0
Se|D|M
6
Ju|S
5
J(?=.*(~))?
$#1
(.+)(w.+)
$2$1
( is \d)(.+)
$2$1
.+(\d).+(\d).+(\d).+(\d).*
$2;$2$4,777$1$3
\d
$*
(1*),\1

1*;(1{7})+$
Yes.
;

(1{7})+(1*)
No, it will be a $.2day.
6
Mon
5
Sun
4
Satur
3
Fri
2
Thurs
1
Wednes
0
Tues

Try it online! Link includes test cases. Explanation:

no.*
$&~

If this is no(t) a leap year, then suffix a ~, so that we can adjust the relative offsets of January and February.

Feb(?=.*~)|Mar|N|Sa
4

The relative offset of February, when not in a leap year, is the same as March and November.

F|Au
3

But in a leap year, it's the same as August. And with my arbitrary numbering convention based on Tuesday, it's also the relative offset of Friday.

Ma|Th
2
O|We
1
A|Jul|T
0
Se|D|M
6
Ju|S
5

Similarly for the remaining months and days of the week.

J(?=.*(~))?
$#1

January's offset is simply the count of ~s.

(.+)(w.+)
$2$1

If the will (Month) start on (Day) is not at the beginning, then move it there.

( is \d)(.+)
$2$1

If the month is (Month) is not at the end, then move it there. This means that the order is now (target month offset), (target day offset), (source day offset), (source month offset).

.+(\d).+(\d).+(\d).+(\d).*
$2;$2$4,777$1$3

Form the values (target day offset); (target day offset) + (source month offset), 11 + (target month offset) + (source day offset). The 21 ensures that the difference is at least 7.

\d
$*
(1*),\1

Convert the values to unary so that the difference between the sums of offsets can be taken.

1*;(1{7})+$
Yes.

If the difference is zero (modulo 7), then the offsets matched, so that the target day is correct.

;

Add the difference to the target day offset to get the correct day offset.

(1{7})+(1*)
No, it will be a $.2day.

Convert the day offset (modulo 7) back to decimal.

6
Mon
5
Sun
4
Satur
3
Fri
2
Thurs
1
Wednes
0
Tues

Decode it into a day of the week.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

CJam, 331 318 316 289 290 bytes

-13 bytes by having the weekday function spit out a string which can be compared directly to the natural-language word given.

-2 bytes by removing a -2 offset in weekday calculation.

-27 bytes with an alternate method of converting a month name to a number from 1 to 12.

+1 byte to correct a typo.

l);" and if"4/{/',*}/',/{S/(;}/_0='w#{]:\}&[)\-4={A="bMAanlseovc"#))}:R~]])\{_-2=0="lia"#"3=c'a=W=R:M;W=:O;"6/=~}/:L;[M0]{))+_{[~_2$3<_!-2*@@- 4/_25/_4/\W*](23*9/+:+7%"Fri Satur Sun Mon Tues Wednes Thurs"S/="day"+}:T~\_1=1e2md_4%!\!@4%!*-L=@O=*!}g)@(\@+T_@="Yes"@"No, it will be a "\+?\;'.+

Try it online!

Oh boy. This was a doozy.

Quick write-up:

l);                    e# Read input, and remove question mark.
" and if"4/{/',*}/',/  e# Replace " and" and " if" with commas, then split into clauses.
{S/(;}/                e# Split each clause into words, and remove the first "word".
                       e# (It will be "If" or "Will" if they are capitalized, empty otherwise.)
_0='w#                 e# If "w" is not the last clause's first word's first character...
{]:\}&                 e# ...the "will" clause is not on top; bring it to the top.
[                      e# We will parse its month and weekday, and put them in an array.
)                      e# Raw weekday string from input.
\-4={A="bMAanlseovc"#))}:R~  e# Month to number by looking up the character "at" index 10.
                       e# (If month name is less than 11 chars, the index wraps around.)
]                      e# Now we've collected these into an array.
])\                    e# Collect clauses in array and bring to top.
{_-2=0="lia"#          e# Use the first character of the second-to-last word in the clause...
"3=c'a=W=R:M;W=:O;"6/  e# ...to index into one of three pieces of 6(-ish)-character code...
=~}/                   e# ...to run on each clause to extract the relevant information.
:L;                    e# Now O has the weekday, M the month, and L the leap-year status.
[M0]{                  e# We'll use M to get the first year that matches the criteria.
))+_                   e# Increment the year, and put it back in the array.
{[~_2$3<_!-2*@@- 4/_25/_4/\W*](23*9/+:+7%"Fri Satur Sun Mon Tues Wednes Thurs"S/="day"+}:T~  e# Weekday of day 1, given [month year].
                       e# (Algorithm adapted from http://cadaeic.net/calendar.htm)
\_1=1e2md_4%!\!@4%!*-  e# Check if year is leap year.
L=@O=*!}g              e# Run this loop until leap-year and weekday criteria match the year.
)@(\@+T                e# Get weekday of requested month with calculated year...
_@=                    e# ...then compare it with requested weekday.
"Yes"@"No, it will be a "\+?\;'.+  e# Choose the right string based on the weekday.

Alternate 290-byter

This works in almost the same way, except it uses a LUT to determine the month offset in the weekday calculation, instead of math.

l);" and if"4/{/',*}/',/{S/(;}/_0='w#{]:\}&[)\-4={A="bMAanlseovc"#}:R~]])\{_-2=0="lia"#"3=c'a=W=R:M;W=:O;"6/=~}/:L;[M0]{))+_{[~_2$1<- 4/_25/_4/\W*](2457931901 7b=+:+7%"Fri Satur Sun Mon Tues Wednes Thurs"S/="day"+}:T~\_1=1e2md_4%!\!@4%!*-L=@O=*!}g)@(\@+T_@="Yes"@"No, it will be a "\+?\;'.+

Try it online!

Random snippets

Some fun little snippets to experiment with on your own:

[~_2$3<_!-2*@@- 4/_25/_4/\W*](23*9/+:+7% - takes [month year] on stack, returns weekday with Friday=0 to Thursday=6

1e2md_4%!\!@4%!*- - takes year on stack, if result is nonzero then that is a leap year

60b73%W*7,= - takes capitalized weekday string on stack, returns index into week with Wednesday=0 to Tuesday=6

134b395%7% - same as above, but with Tuesday=0 to Monday=6

75b501%8% - same as above, but with Saturday=0 to Friday=6

4b502%9% - same as above, but with Sunday=0 to Saturday=6

A="bMAanlseovc"# - takes month on stack, returns month number with January=-1 to December=10

| improve this answer | |
\$\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.