4

This should be an easy question! I want to define a function with domain = the 12 integers {1,12}, with the values f[1]=31, f[2]=28, f[3]=31, etc. (number of days in the month). This will be a part of nested Do[] loops running through the days of a non-leap year for a particular data set I am working with.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
R. Peter DeLong
  • 445
  • 4
  • 11
  • 1
    It seems that within the statement of the question you have already gone one-quarter of the way to defining your entire function. Why not just complete the process? – whuber Mar 05 '12 at 18:25
  • @whuber Because it doesn't scale if he later wants to have f[m,d] = <hours per day>. Who'd want to type all 365(6) entries in by hand? – Brett Champion Mar 05 '12 at 18:40
  • I didn't see any scaling requirement in the question, Brett. Sometimes the obvious method can be the best solution. – whuber Mar 05 '12 at 18:43
  • 1
    @whuber Well, there goes my attempt at humor for the day... – Brett Champion Mar 06 '12 at 01:27
  • 2
    Too subtle for me, I guess. How can it be humor without a smiley? :-) – whuber Mar 06 '12 at 04:25

6 Answers6

13

I advise against using Switch to implement this function because it is considerably slower than other pattern matching.

Here is the AbsoluteTiming for the Switch method on my machine:

f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]

f /@ RandomInteger[20, 500000]; // AbsoluteTiming
{1.3250758, Null}

Here is the same thing avoiding Switch and Condition:

g[2] = 28;
g[4 | 6 | 9 | 11] = 30;
g[1 | 3 | 5 | 7 | 8 | 10 | 12] = 31;

g /@ RandomInteger[20, 500000]; // AbsoluteTiming
{0.2070119, Null}

Here is direct definition for each value (h[1] = 31; h[2] = 28; . . .):

months = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

Inner[(h[#] = #2) &, Range@12, months, List];

h /@ RandomInteger[20, 500000]; // AbsoluteTiming
{0.1950112, Null}

Here is a related operation using a Dispatch table and Replace. All values other than (1 .. 12) are replaced with zero:

rls = Dispatch @ Append[Thread[Range[12] -> months], _ -> 0];

Replace[RandomInteger[20, 500000], rls, {1}]; // AbsoluteTiming
{0.0670038, Null}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 1
    Wow. This is very interesting. In my application it doesn't matter because the function only gets called 12 times in a Do[ ] loop, but this would be very important in an iterated application. – R. Peter DeLong Mar 06 '12 at 21:49
11

Try

f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]
kglr
  • 394,356
  • 18
  • 477
  • 896
6

How about this, which takes the name of the variable (eg f) as an argument, uses Mathematicas' date functionality to obtain the last day of each month in a given year and defines f[n] as the number of days in month n:

def[year_, var_] := MapThread[
  (var[#1] = #2) &,
  {
   Range[12],
   Part[
    DatePlus[{year - 1, 12, 31}, {#, "Month"}] & /@ Range[12],
    All, 3
    ]
   }
  ]

eg, for 2011 (which was not leap)

def[2011, f]
(*{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}*)

and then eg

f[2]

gives 28. On the other hand,

def[2008, f]
(*{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}*)

takes into account the fact that 2008 was leap.

EDIT: Note that this defines DownValues for var, or f in the example above, as may be seen from either ?f or DownValues[f].

acl
  • 19,834
  • 3
  • 66
  • 91
  • Another great solution, but more than I need at this moment, since I am combining data from several years at once (and I ignore 29 Feb). Thanks! – R. Peter DeLong Mar 05 '12 at 18:42
6

Here's a solution making use of the fact that you can assign to a list of variables:

Set[Evaluate[f /@ Range[12]], {31,28,31,30,31,30,31,31,30,31,30,31}]

although with this approach you can only do it once. (Otherwise the Evaluate will turn f[1] into 31 before assignment occurs, and you'll get an error.)

Brett Champion
  • 20,779
  • 2
  • 64
  • 121
5

This is probably the simplest way to define it:

(f[#]=31)&/@Range[12]
(f[#]=30)&/@{4,6,9,11} 
f[2]=28
celtschk
  • 19,133
  • 1
  • 51
  • 106
3

Another approach:

Do[f[n] = 30 + Boole[Xor[OddQ[n], n>7]] - 2 Boole[n == 2], {n,12}]

One could also do the calculation at run time:

f[n_Integer /; 1 <= n <= 12] :=
  30 + Boole[Xor[OddQ[n], n>7]] - 2 Boole[n == 2]

Or with memoization:

f[n_Integer /; 1 <= n <= 12] :=
  f[n] = 30 + Boole[Xor[OddQ[n], n>7]] - 2 Boole[n == 2]
celtschk
  • 19,133
  • 1
  • 51
  • 106