103
\$\begingroup\$

Aww, man, this expiry date doesn't write the months with letters! I can't tell if it's expiring on March 10th or October 3rd... Wait, no, never mind, the year says 2012. (alley-oops half-used brick of cheese into the trash can like a pro)

So let's suppose for a moment that you're too busy to try to reason out when this jar of marinara is supposed to expire. You just want the Cliff Notes version: how likely is it that it's past due? Let's write some code!

You know that the manufacturers print the date as an ordered triple of integers, in one of three formats:

YEAR  MONTH DAY
MONTH DAY   YEAR
DAY   MONTH YEAR

And you know that some dates can only be interpreted in one or two ways, not all three: the 55 in 55-11-5 has to be a year, meaning this particular box of Twinkies expired November 5th, 1955. The year is sometimes given in four digits and not two, which can rule out some options. When it's two digits, though, 50..99 means 1950..1999 and 0..49 means 2000..2049.

Your job is to write a program or function that takes an array of integers which is a valid date in at least one of the interpretations above, and outputs a percent chance it is still good. The percent chance is simply the percentage of valid interpretations of the date that are on or later than today's date.

The array of integers will be your language's [Int] type of length three if it is an argument to a function, and given as either dash-, slash-, or space-separated (you get to pick) integers if used as input on STDIN to a full program.*

"Today's date" can be today's actual date, as obtained through a date function, or the date given in an extra argument to function or extra paramater in STDIN. It may be in Unix epoch seconds, another year-month-day triple entered in one of the three ways above, or another more convenient fashion.

Let's have some examples! The expiry date input will be in the dash-separated style, and assume for the examples below that today's date is July 5th, 2006.

  • 14-12-14 - Both valid interpretations for this (DMY and YMD) are equivalent, December 14, 2014. The output is 100 because this product is definitely still good.
  • 8-2-2006 - The last number is a year, for sure, since it has four digits. This could be either February 8th (expired) or August 2nd (still good). The output is 50.
  • 6-7-5 - This could be anything! The "July 5th, 2006" interpretation is still good (for one day only), but the remaining two are both in 2005 and should be tossed as quickly as possible. The output is 33.
  • 6-5-7 - Here, two out of three interpretations are safe. You can round your decimal up or down, so 66 or 67 are both okay.
  • 12-31-99 - Okay, this one is unambiguously from the turn of the century (years from 50 to 99 are 19XX, and 31 can't possibly be a month). A big fat 0, and you really should clean out your fridge more often.

You can safely assume that any input which does not meet the standards above is not privy to the output rules above.

No web requests or standard loopholes. Date handling libraries are allowed. This is code golf: may the shortest program win.

* If you are using brainfuck or some similarly datatype-handicapped language, you can assume the ASCII values of the first three characters in input are the integers for the date. This excludes the four-digit year logic, sure, but I think we would be too astounded by seeing a solution to this in Brainfuck to slight you for it.

\$\endgroup\$
21
  • 41
    \$\begingroup\$ Umm... the current year is 2014, not 2006. Your milk is eight years past its expiry at best. \$\endgroup\$ Jul 6, 2014 at 1:32
  • 11
    \$\begingroup\$ @JanDvorak I just didn't want to try very hard to construct meaningful examples, so I tweaked today's date to make it easier. \$\endgroup\$ Jul 6, 2014 at 2:08
  • 7
    \$\begingroup\$ @Dgrin91 don't care, I'll still eat them :D \$\endgroup\$ Jul 6, 2014 at 15:35
  • 7
    \$\begingroup\$ In Australia, the milk expires about a week before the use by date \$\endgroup\$
    – gnibbler
    Jul 7, 2014 at 5:24
  • 5
    \$\begingroup\$ You should add a test with a 00 in it, since that can't be a legal day or month. \$\endgroup\$ Jul 8, 2014 at 5:59

14 Answers 14

15
\$\begingroup\$

Ruby, 115 characters

f=->a,t{[a,a.rotate(~s=r=0),a.reverse].map{|x,*y|(t>Time.gm(x<100?x+2e3-100*x/=50:x,*y)||r+=100
s+=1)rescue p}
r/s}

This defines a function f that takes two arguments: an array containing the input, and "today's" date.

Examples:

f[[14,12,14], Time.new]
100
f[[8,2,2006], Time.new]
0
f[[8,2,2006], Time.new(2006, 7, 5)]
50
f[[6,7,5], Time.new(2006, 7, 5)]
33
\$\endgroup\$
13
\$\begingroup\$

Python 2.7 - 172

I use the datetime module for validity and comparison of dates. If date can't make a valid datetime out of the input, it raises ValueError. This way s is the sum of the non-expired dates and t is the total number of valid dates. I'm taking advantage of the fact that True == 1 for the purposes of addition and indexing in Python. I also save a character by using 25*(76,80) instead of (1900,2000).

Note the lines in the second level of indentation use a tab character, not 2 spaces.

def f(e,c,s=0,t=3):
 for Y,M,D in(0,1,2),(2,0,1),(2,1,0):
  y=e[Y]
  try:s+=date(y+25*[[76,80][y<50],0][y>99],e[M],e[D])>=c
  except:t-=1
 return 100*s/t

Add this to the end to test:

examples = [[14,12,14],[8,2,2006],[6,7,5],[6,5,7],[12,31,99]]
for e in examples:
 print f(e, date(2006,7,5))
\$\endgroup\$
0
10
\$\begingroup\$

PowerShell, 183 173 168

[int](100*(($d=@(($a,$b,$c=$args[0]),($c,$a,$b),($c,$b,$a)|%{$_[0]+=1900*($_[0]-le99)+100*($_[0]-le49)
.{date($_-join'-')}2>$x}|sort -u))-ge(date)+'-1').Count/$d.Count)
  • Input as int[] via parameter, e.g.

      PS> ./milk.ps1 5,6,7
    
  • Error messages are silenced via try/catch, as long as I don't know whether output on stderr is allowed or not.

  • Using +"-1" on the date, which gets interpreted as .AddDays(-1) to shift the current date by one day, so that we can compare to yesterday (instead of just today). This solves the problem that we get a date with 0:00 as time but need to compare with a date with time from today.

  • Heavily inlined by now

  • Using a new trick for silencing errors that's quite a bit shorter

\$\endgroup\$
6
\$\begingroup\$

R, 269

I was expecting this to be easy in R, but the single-digit years were a pretty big curveball. I feel like this could be much better than it is.

lubridate is a package from CRAN, you might need to install it with install.packages("lubridate").

require(lubridate)
f = function(d){
d=sapply(d,function(l)if(nchar(l)==1)sprintf("%02d",l)else l)
d=paste0(d,collapse="-")
t=ymd(Sys.Date())
s=na.omit(c(ymd(d),mdy(d),dmy(d)))
s=lapply(s,function(d){
if(year(d)>2049){year(d)=year(d)-100;d}
else d})
sum(s>t)/length(s)}

Usage: f(c(d1,d2,d3)) where c(d1,d2,d3) is a vector of integers.

e.g. f(c(6,10,14)) returns 0.3333333.

The lubridate package has a series of wrapper functions for parsing dates in different orders. I use these to see which formats produce valid dates, throw out the invalid ones, and then see which ones haven't occurred yet.

\$\endgroup\$
6
\$\begingroup\$

Mathematica, 163 153 164 bytes

(edit: fixed dates outside the 1950 - 2049 range)

f=100.Count[#,x_/;x<1]/Length@#&[DateDifference[#,Date[]]&/@Cases[{If[#<100,Mod[#+50,100]+1950,#],##2}&@@@{{##},{#3,#2,#},{#3,#,#2}}&@@#,d_/;DateList@d~Take~3==d]]&

This defines a function which you can call like

f[{6,7,5}]

Currently, the percentage isn't rounded (waiting for the OP to clarify).

Here is a slightly lengthy explanation that should be understandable without any Mathematica knowledge (note that & makes everything left of it an anonymous function whose parameters are referred to as #, #2, #3...):

{{##},{#3,#2,#},{#3,#,#2}}&

This defines a function, which turns 3 parameters a,b,c into 3 lists {{a,b,c},{c,b,a},{c,a,b}. Note that ## is just a sequence of all parameters.

{{##},{#3,#2,#},{#3,#,#2}}&@@#

Applied to the expiry date, this gives a list of {y,m,d} for each of the three possible permutations.

{If[#<100,Mod[#+50,100]+1950,#],##2}&

This is an anonymous function that takes three parameters a,b,c and returns a list of the three, where the first has been converted to a year as per the given rules: numbers between 50 and 99 (modulo 100) are turned into a 20th century year, numbers between 0 and 49 (modulo 100) are turned into a 21st century year, all others are left along. Here, ##2 is a sequence of parameters starting with the second one, i.e. b,c.

{If[#<100,Mod[#+50,100]+1950,#],##2}&@@@{{##},{#3,#2,#},{#3,#,#2}}&@@#

Applied to each of the three previous results, this just canonicalises the year formats. Let's call this canonicalDates to shorten the following expression:

Cases[canonicalDates,d_/;DateList@d~Take~3==d]

This filters out invalid interpretations. DateList@d makes a full {y,m,d,h,m,s} representation out of various date formats. It will interpret lists in the same order, but the catch is that you can pass it things like {8,2,2006} in which case it will calculate 8 years + 2 months + 2006 days. So we check that the first three elements of the returned list are identical to the input (which can only happen if the month and day in the appropriate ranges).

To shorten the following lines, I'll refer to the result of that expression as validDates from now on:

DateDifference[#,Date[]]&

Another anonymous function which takes a date and returns the difference in days to today (obtained from Date[]).

DateDifference[#,Date[]]&/@validDates

Map that onto the valid date interpretations.

100.Count[#,x_/;x<1]/Length@#&

Yet another anonymous function which, given a list (#), returns the percentage of non-positive numbers in that list. The . is not a multiplication but just the decimal digit, to avoid rational numbers as the result (you'd get things like 100/3 instead of 33.333 - I don't actually know if that's a problem).

100.Count[#,x_/;x<1]/Length@#&[DateDifference[#,Date[]]&/@validDates]

Applied to the list of date differences, this gives us the fraction of interpretations which are not yet expired.

\$\endgroup\$
5
  • \$\begingroup\$ I think you incorrectly convert years like 2999 or 2099 to 1999. \$\endgroup\$
    – Ventero
    Jul 6, 2014 at 18:42
  • \$\begingroup\$ @Ventero that's true. I kinda assumed we were only dealing with years 1950 - 2049 (and their 1 or 2 digit versions), but re-reading the challenge there's no mention of that. \$\endgroup\$ Jul 6, 2014 at 18:59
  • \$\begingroup\$ @Ventero fixed (but you had already beaten me significantly anyway ;)) \$\endgroup\$ Jul 7, 2014 at 1:23
  • \$\begingroup\$ I am surprised to see that you have an account on Mathematica but haven't posted any Questions or Answers. Is something holding you back? \$\endgroup\$
    – Mr.Wizard
    Jul 9, 2014 at 6:54
  • \$\begingroup\$ @Mr.Wizard sorry, totally forgot to respond to you. Questions: so far every problem I had could be solved with googling/other SE questions. Answers: I don't know... I guess I don't view myself as that proficient when it comes to using Mathematica productively... I only use it for quick snippets here and there (and code golf). Also, I guess to answer questions I'd have to actively watch new ones to see what I can answer, and currently all my SE time is allocated for PPCG. ;) If you want to me convince me otherwise, feel free to do so in chat! :) \$\endgroup\$ Jul 16, 2014 at 17:52
5
\$\begingroup\$

k4 (90) (88) (87) (82)

{100*(+/~d<x)%3-+/^d:{"D"$"."/:$|z,y,x+(x<100)*100*19+x<50}.'y@/:3 3#.:'$21020101}

Invoke with x of .z.D (a builtin) for comparison to today, or a date literal of your choice otherwise:

  f:{100*(+/~d<x)%3-+/^d:{"D"$"."/:$|z,y,x+(x<100)*100*19+x<50}.'y@/:3 3#.:'$21020101}
  .z.D f'(14 12 14;8 2 2006;6 7 5;6 5 7;12 31 99)
100 0 0 0 0f
  2006.07.05 f'(14 12 14;8 2 2006;6 7 5;6 5 7;12 31 99)
100 50 33.33333 66.66667 0

This is basically a port of @Alex-l's Python solution, with a few miscellaneous golfing tricks added:

  • The rearrangement instructions are encoded in a string to save a couple characters.
  • The conditional logic (ab)uses truth-as-integer (but in a different way from the Python solution).
  • The validity test is slightly different--k4/q will happily parse any string into any datatype; it simply returns a null if it can't make sense of it. Thus, I return a list of dates from the inner function, which may or may not be null.
  • The final result comes from checking how many of the possible date interpretations are null vs. how many are less than the comparison date; it's important here that the null date is considered less than any other date.
\$\endgroup\$
4
  • 1
    \$\begingroup\$ You can save a char by removing the last 0 from "012201210", since # takes its items cyclically. In fact, you can save a second char this way by swapping the last two cases: 3 3#.:'"0122102". \$\endgroup\$ Jul 8, 2014 at 17:23
  • \$\begingroup\$ Shaved one more char by reversing args of inner func, saving the parens (but adding a reverse). Can anyone help me save another two chars? APL is beating me! \$\endgroup\$ Jul 10, 2014 at 23:31
  • \$\begingroup\$ Shaved another five by rewriting the math at the end. Back in the lead! \$\endgroup\$ Jul 10, 2014 at 23:50
  • \$\begingroup\$ And if I stoop to writing seriously non-functional code, I can shave another byte by polluting the global namespace: {c*(+/~d<x)%3-+/^d:{"D"$"."/:$|z,y,x+(c*19+x<50)*x<c::100}.'y@/:3 3#.:'$21020101}. \$\endgroup\$ Jul 17, 2014 at 22:59
4
\$\begingroup\$

JavaScript (E6) 159 164 172

Edit Thanks to nderscore for the hints and for pushing me to think again. Reorganized D avoiding parameters and cutting some chars.

Edit 2 Another trick by nderscore, 2 functions merged into 1. Then two parenthesis removed merging comma separated expressions into one. Readability near 0. Sidenote: Not rounding could save another 2 chars (|0).

F=(a,t)=>t?100*(3-((i=F([y,m,d]=a))<t)-((j=F([m,d,y]=a))<t)-((k=F([d,m]=a))<t))/(3-!i-!j-!k)|0:(q=new Date(y<50?y+2e3:y,--m,d)).getMonth()==m&q.getDate()==d&&q

Test In FireFox console

;[[14,12,14],[8,2,2006],[6,7,5],[6,5,7],[12,31,99]]
.map(x=>x + ' ' + F(x, new Date(2006,6,5)))

Output:

["14,12,14 100", "8,2,2006 50", "6,7,5 33", "6,5,7 66", "12,31,99 0"]

Ungolfed

NB D function tries to create a Date with given year, month, day but returns false if the created date is not what was intented (!= day or month)

F=(d,t)=>
(
  D=(y,m,d)=>(
    q=new Date(y<50?y+2000:y, --m, d), // decr m as javascript (like java) counts months starting at 0
    q.getMonth() == m & q.getDate() == d && q
  ),
  [a,b,c] = d, 
  x=D(...d), // three ways of express the date ...
  y=D(c,a,b),
  z=D(c,b,a),
  100 * (3-(x<t)-(y<t)-(z<t)) / (3-!x-!y-!z) | 0  
)   
\$\endgroup\$
3
  • \$\begingroup\$ @nderscore OK for changes in D, sintax error for the other. But saved even more anyway \$\endgroup\$
    – edc65
    Jul 7, 2014 at 20:18
  • \$\begingroup\$ Weird. Something must've happened when I pasted it into the comment. Your latest optimizations make it irrelevant though :) \$\endgroup\$
    – nderscore
    Jul 7, 2014 at 20:42
  • 1
    \$\begingroup\$ Putting this in a paste, as I don't trust SE's comments anymore: (-3) pastie.org/private/6bemdweyndcaiseay70kia \$\endgroup\$
    – nderscore
    Jul 7, 2014 at 20:59
4
\$\begingroup\$

C# in LINQPad - 446 408 272 Bytes

Third Edit: Thanks to Le Canard fou for pointing out that DateTime.Today is correct, not DateTime.Now. Second Edit: Thanks VisualMelon for this clever solution!

void g(int[]d){var p=".";int a=d[2],b=d[1],e=d[0],y=a+(a<100?a>49?1900:2000:0),q=0,s=0;DateTime c;Action<string>z=x=>{if(DateTime.TryParse(x,out c)){s++;if(c>=DateTime.Today)q+=100;}};z(e+p+b+p+y);z(b+p+e+p+y);z(a+p+b+p+(e<100?‌​e>49?1900+e:2000+e:e));(q/(s>0?s:1)).Dump();}

Edit: Thanks to podiluska and edc65 for helping me shorting the code! I also noticed that my solution wasn't correct if the year input was 4 bytes long, therefore I included the fix for that problem. The score for this solution is 408 Bytes.

Even though I'm not beating any of the previous answers, I still wanted to share my C# solution. Any help/suggestions are appreciated! ;)

void g(int[]d){var q=new List<DateTime>();var p=".";int s=0,a=d[2],b=d[1],e=d[0],y=0;var c=new DateTime();y=(a<100)?(a>49)?1900+a:2000+a:a;if(DateTime.TryParse(e+p+b+p+y,out c)){q.Add(c);s++;}if(DateTime.TryParse(b+p+e+p+y,out c)){q.Add(c);s++;}y=(e<100)?(e>49)?1900+e:2000+e:e;if(DateTime.TryParse(a+p+b+p+y,out c)){q.Add(c);s++;}q=q.Where(i=>i>=DateTime.Now).ToList();if(s==0){s=1;}(q.Count*100/s).Dump();}

Formatted and ungolfed version:

void g(int[] d)
    {
        var q = new List<DateTime>();
        var p = ".";
        int s = 0, a = d[2],b = d[1],e = d[0], y=0;
        var c = new DateTime();
        y = (a < 100) ?((a > 49) ? 1900 + a : 2000 + a) : a;

        if (DateTime.TryParse(e + p + b + p + y, out c))
        {
            q.Add(c);
            s++;
        }
        if (DateTime.TryParse(b + p + e + p + y, out c))
        {
            q.Add(c);
            s++;
        }
        y = (e < 100) ? ((e > 49) ? 1900 + e : 2000 + e) : e;

        if (DateTime.TryParse(a + p + b + p + y, out c))
        {
            q.Add(c);
            s++;
        }
        q = q.Where(i => i >= DateTime.Now).ToList();
        if (s == 0)
        {
            s = 1;
        }
        (q.Count*100/s).Dump();
    }

I tried to make a solution where the "DateTime.TryParse"-Part isn't repeated as in this solution, but it was 21 bytes longer.

Solution without repeating "DateTime.TryParse" : 467 Bytes

void g(int[]d){var q=new List<DateTime>();int s=0;int a=d[2];int b=d[1];int e=d[0];int y=0;if(a<100){if(a>49){y=1900+a;}else{y=2000+a;}}if(z(e,b,y,q)){s++;}if(z(b,e,y,q)){s++;}if(e<100){if(e>49){y=1900+e;}else{y=2000+e;}}if(z(a,b,y,q)){s++;}q=q.Where(i=>i>=DateTime.Now).ToList();if(s==0){s=1;}(q.Count*100/s).Dump();}bool z(int a,int b,int d,List<DateTime> q){var c=new DateTime();var p=".";if(DateTime.TryParse(a+p+b+p+d,out c)){q.Add(c);return true;}return false;}

Ungolfed version:

private void g(int[] d)
    {
        var q = new List<DateTime>();
        int s = 0;
        int a = d[2];
        int b = d[1];
        int e = d[0];
        int y = 0;
        if (a < 100)
        {
            if (a > 49)
            {
                y = 1900 + a;
            }
            else
            {
                y = 2000 + a;
            }
        }
        if (z(e, b, y, q))
        {
            s++;
        }
        if (z(b, e, y, q))
        {
            s++;
        }
        if (e < 100)
        {
            if (e > 49)
            {
                y = 1900 + e;
            }
            else
            {
                y = 2000 + e;
            }
        }
        if (z(a, b, y, q))
        {
            s++;
        }
        q = q.Where(i => i >= DateTime.Now).ToList();
        if (s == 0)
        {
            s = 1;
        }
        (q.Count*100/s).Dump();
    }

    private bool z(int a, int b, int d, List<DateTime> q)
    {
        var c = new DateTime();
        string p = ".";
        if (DateTime.TryParse(a + p + b + p + d, out c))
        {
            q.Add(c);
            return true;
        }
        return false;
    }
\$\endgroup\$
10
  • 2
    \$\begingroup\$ int s=0;int a=d[2];int b=d[1];int e=d[0]; -> int s=0,a=d[2],b=d[1],e=d[0]; \$\endgroup\$
    – podiluska
    Jul 7, 2014 at 13:43
  • 2
    \$\begingroup\$ suggestion: use ternary (?:) when possible instead of if/else \$\endgroup\$
    – edc65
    Jul 7, 2014 at 20:29
  • 1
    \$\begingroup\$ @ThomasW. I don't think since y has 2 different values, one time it is depending on a, the other time it is depending on e. Thanks anyway! \$\endgroup\$
    – tsavinho
    Jul 8, 2014 at 8:47
  • 1
    \$\begingroup\$ Removing the DateTime.TryParse calls was my first instinct, replaced it with a lambda that also put the value back into q. Also performed some other steps (pastebin) to get 328chars: void g(int[]d){var q=new List<DateTime>();var p=".";int a=d[2],b=d[1],e=d[0],y;DateTime c;y=(a<100)?(a>49)?1900+a:2000+a:a;Action<string>z=(x)=>{if(DateTime.TryParse(x,out c))q.Add(c);};z(e+p+b+p+y);z(b+p+e+p+y);y=(e<100)?(e>49)?1900+e:2000+e:e;z(a+p+b+p+y);(q.Where(i=>i>=DateTime.Now).Count()*100/(q.Any()?q.Count:1)).Dump();} \$\endgroup\$ Jul 8, 2014 at 12:42
  • 1
    \$\begingroup\$ @VisualMelon Wow, you're really good at code-golfing! I never saw Action<string> before, so I could learn something from you ;) I was able to get your answer down to 318 chars by replacing q.Where(i=>i>=DateTime.Now).Count with q.Count(i=>i>=DateTime.Now. I also removed the brackets around x so I could save 2 more characters! \$\endgroup\$
    – tsavinho
    Jul 8, 2014 at 14:09
3
\$\begingroup\$

Haskell, 171 165 characters

l=length
r y|y<100=(y+50)`mod`100+1950|y>0=y
q d m y z|d<32&&m<13&&d*m>0=(r y,m,d):z|1<3=z
v(a,b,c)=q c b a$q b a c$q a b c[]
t%d=(l$filter(>t)(v d))*100`div`l(v d)

The function's name is %. Run with the test date as an tuple in canonical (y,m,d) order with actual year, and the carton stamp as a tuple of three numbers:

λ: (2006,6,5)%(14,12,14)
100

λ: (2006,6,5)%(8,2,2006)
50

λ: (2006,6,5)%(6,7,5)
33

λ: (2006,6,5)%(6,5,7)
66

λ: (2006,6,5)%(12,31,99)
0

λ: (2006,6,5)%(0,1,7)
0
\$\endgroup\$
2
\$\begingroup\$

Erlang, 146

f([A,B,C]=U,N)->F=[T||T<-[{(Y+50)rem 100+1950,M,D}||[Y,M,D]<-[U,[C,A,B],[C,B,A]]],calendar:valid_date(T)],100*length([1||T<-F,T>=N])div length(F).

Test function would be:

t() ->
    0 = f([12,31,99],{2006,6,5}),
    66 = f([6,5,7],{2006,6,5}),
    33 = f([6,7,5],{2006,6,5}),
    100 = f([14,12,14],{2006,6,5}),
    50 = f([8,2,2006],{2006,6,5}),
    100 = f([29,2,2],{2006,6,5}).

Ungolfed

f([A,B,C]=U,Today)->
    Perms = [U,[C,A,B],[C,B,A]],
    WithYears = [{(Y+50) rem 100+1950,M,D} || [Y,M,D] <- Perms],
    ValidDates = [T || T <- WithYears, calendar:valid_date(T)],
    100*length([1 || T <- ValidDates, T >= Today]) div length(ValidDates).

This solution relies on list comprehensions. It borrows the modulo trick for the year from the Haskell solution. It also uses calendar:valid_date/1 to handle impossible dates because of the number of days in a given month (e.g. "29-2-2" can only be in YMD format). Also, Today is in Erlang's date() format (a YMD tuple).

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

APL (85)

This uses some of Dyalog APL 14's new functions, but no external libraries. For a change, it works on TryAPL.

{100×(+/÷⍴)⍺≤{(3/100)⊥⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵}¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵}

This is a function that takes the 3-element array as its right side () argument, and the date to check against as its left side () argument, as an integer of YYYYMMDD format. I.e., the date 2014-07-09 is represented as the number 20140709.

Test:

      20060705 {100×(+/÷⍴)⍺≤{(3/100)⊥⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵}¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵} 14 12 14
100
      20060705 {100×(+/÷⍴)⍺≤{(3/100)⊥⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵}¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵} 8 2 2006
50
      20060705 {100×(+/÷⍴)⍺≤{(3/100)⊥⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵}¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵} 6 7 5
33.3333
      20060705 {100×(+/÷⍴)⍺≤{(3/100)⊥⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵}¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵} 12 31 99
0

Explanation:

  • Z←(⊂⌽⍵),(⊂2⌽⍵),⊂⍵: turn the given date into Y-M-D format by flipping (⊂⌽⍵), rotating it to the left by 2 (⊂2⌽⍵), or just doing nothing ⊂⍵. At least one of these is now a proper date in Y-M-D format, maybe more than one if the date is ambiguous.
  • {∧/12 31≥1↓⍵}¨Z: test if each date is valid: the year (first element) is dropped, and then the month must be no higher than 12 and the day must be no higher than 31.
  • Z/⍨: filter the valid dates from Z.
  • {...: for each valid date:
    • ⍵+(99≥⊃⍵)×3↑1900+100×50>⊃⍵: if the year is not higher than 99, add 1900, then 100 if the year is lower than 50.
    • (3/100)⊥: decode it as if it were a set of base-100 numbers. (The year is higher than 100, but this doesn't matter as it's the first element.) This gives a number for each valid date in the same format as the left argument.
  • ⍺≤: for each date, see if it is not smaller than . This will give a binary vector where 1 means OK and 0 means spoiled.
  • 100×(+/÷⍴): divide the sum of the binary vector by its length and multiply by 100.
\$\endgroup\$
1
  • \$\begingroup\$ Save 7 bytes (and beat K by a nice margin) with stranding and making an inner function tacit: {100×(+/÷⍴)⍺≤((3/100)⊥⊢+(99≥⊃)×3↑1900+100×50>⊃)¨Z/⍨{∧/12 31≥1↓⍵}¨Z←(⌽⍵)(2⌽⍵)⍵} \$\endgroup\$
    – Adám
    May 29, 2017 at 12:05
0
\$\begingroup\$

Java: 349 Characters (3 w/o spaces)

int e(int[]n,Date t){int a=n[0],b=n[1],c=n[2];Date[]d=new Date[3];if(b<13&&c<32)d[0]=new Date((a<50?100:(a>100?-1900:0))+a,b-1,c);if(b<13&&a<32)d[1]=new Date((c<50?100:(c>100?-1900:0))+c,b-1,a);if(a<13&&b<32)d[2]=new Date((c<50?100:(c>100?-1900:0))+c,a-1,b);int v=0,g=0;for(int i=0;i<3;i++)if(d[i]!=null){if(!d[i].before(t))g++;v++;}return 100*g/v;}

Here is a containing class that can be used to test it, including a (slightly) degolfed version of the method:

import java.util.*;
class i{

   int e(int[]n,Date t){
      int a=n[0],b=n[1],c=n[2];
      Date[]d=new Date[3];
      if(b<13&&c<32)d[0]=new Date((a<50?100:(a>100?-1900:0))+a,b-1,c);
      if(b<13&&a<32)d[1]=new Date((c<50?100:(c>100?-1900:0))+c,b-1,a);
      if(a<13&&b<32)d[2]=new Date((c<50?100:(c>100?-1900:0))+c,a-1,b);
      int v=0,g=0;
      for(int i=0;i<3;i++)
         if(d[i]!=null){
            if(!d[i].before(t))
               g++;
            v++;
         }
      return 100*g/v;}

   public static void main(String[] args){
      int[]i=new int[3];
      for(int k=0;k<3;k++)
         i[k] = Integer.parseInt(args[k]);
      int j = new i().e(i,new Date());
      System.out.println(j+"%");
   }   
}

This is my first round of code golf, and I think I figured out why I don't usually see very many Java golfers.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You need to accept an int[] as argument, not three ints. \$\endgroup\$
    – Joey
    Jul 8, 2014 at 5:52
  • \$\begingroup\$ ok, i fixed it. \$\endgroup\$ Jul 8, 2014 at 16:18
0
\$\begingroup\$

C# 287 bytes

namespace System{class E{static float a,o,l;void M(int[]i){int d=i[0],m=i[1],y=i[2],t;if(l<3)try{if(l==1){t=y;y=d;d=t;}if(l++==0){t=d;d=m;m=t;}if(y<100&&(y+=1900)<1950)y+=100;o+=new DateTime(y,m,d)>=DateTime.Today?1:0;a++;if(l<3)i[9]=9;}catch{M(i);throw;}Console.Write(o/a);}}}

First time golfing, looking for advices. Notably, removing bytes due to namespace.

Abusing the fact that only a function is required, not an actual program. Also, the function always results in an uncaught exception.

Ungolfed

namespace System {
    class E {
        static float a, o, l;
        void M(int[] i) {
            int d = i[0], m = i[1], y = i[2], t;
            if (l < 3)
                try {
                    if (l == 1) { 
                        t = y; y = d; d = t; 
                    } 
                    if (l++ == 0) { 
                        t = d; d = m; m = t; 
                    } 
                    if (y < 100 && (y += 1900) < 1950)
                        y += 100; 
                    o += new DateTime(y, m, d) >= DateTime.Today ? 1 : 0; // # not expired
                    a++; // # valid dates
                    if (l < 3)
                        i[9] = 9; // throw new Exception()
                } 
                catch { 
                    M(i);
                    throw; // fail after the first Console.Write()
                } 
            Console.Write(o / a); 
        } 
    } 
}
\$\endgroup\$
0
\$\begingroup\$

Mathematica, 118

Using m.buettner's code as a starting point I have a few improvements:

⌊100Mean@UnitStep@Cases[DateDifference@{If[#<100,Mod[#+50,100]+1950,#],##2}&@@@{#,RotateRight@#,Reverse@#},_Integer]⌋&
\$\endgroup\$
2
  • \$\begingroup\$ The golf may be a function taking a three-Int list as argument. \$\endgroup\$ Jul 9, 2014 at 15:45
  • \$\begingroup\$ @algorithmshark Thanks. I don't know how I missed that. Updating... \$\endgroup\$
    – Mr.Wizard
    Jul 9, 2014 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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