4
$\begingroup$

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.

$\endgroup$
5
  • 1
    $\begingroup$ 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? $\endgroup$
    – whuber
    Commented Mar 5, 2012 at 18:25
  • $\begingroup$ @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? $\endgroup$ Commented Mar 5, 2012 at 18:40
  • $\begingroup$ I didn't see any scaling requirement in the question, Brett. Sometimes the obvious method can be the best solution. $\endgroup$
    – whuber
    Commented Mar 5, 2012 at 18:43
  • 1
    $\begingroup$ @whuber Well, there goes my attempt at humor for the day... $\endgroup$ Commented Mar 6, 2012 at 1:27
  • 2
    $\begingroup$ Too subtle for me, I guess. How can it be humor without a smiley? :-) $\endgroup$
    – whuber
    Commented Mar 6, 2012 at 4:25

6 Answers 6

11
$\begingroup$

Try

f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]
$\endgroup$
1
  • $\begingroup$ Thanks! Nice generalizable solution with tight control of both the domain and range. $\endgroup$ Commented Mar 5, 2012 at 18:37
13
$\begingroup$

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}
$\endgroup$
1
  • 1
    $\begingroup$ 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. $\endgroup$ Commented Mar 6, 2012 at 21:49
6
$\begingroup$

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].

$\endgroup$
1
  • $\begingroup$ 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! $\endgroup$ Commented Mar 5, 2012 at 18:42
6
$\begingroup$

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.)

$\endgroup$
1
  • $\begingroup$ Thanks. I have found the Evaluate[ ] function confusing… This helps. $\endgroup$ Commented Mar 6, 2012 at 21:49
5
$\begingroup$

This is probably the simplest way to define it:

(f[#]=31)&/@Range[12]
(f[#]=30)&/@{4,6,9,11} 
f[2]=28
$\endgroup$
5
  • $\begingroup$ Another great approach. I love learning these tricks. Thanks! $\endgroup$ Commented Mar 5, 2012 at 20:06
  • 1
    $\begingroup$ Scan[] would be a better thing to use here than Map[]. $\endgroup$ Commented May 4, 2012 at 2:59
  • $\begingroup$ @J.M.: Better in what way? $\endgroup$
    – celtschk
    Commented May 4, 2012 at 7:13
  • $\begingroup$ See the discussion under the comments here. $\endgroup$ Commented May 4, 2012 at 7:19
  • $\begingroup$ @J.M.: I see, thanks. $\endgroup$
    – celtschk
    Commented May 4, 2012 at 7:27
3
$\begingroup$

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]
$\endgroup$
1
  • $\begingroup$ Where did you learn this? $\endgroup$
    – Mr.Wizard
    Commented May 4, 2012 at 17:01

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.