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.
- 124,525
- 11
- 401
- 574
- 445
- 4
- 11
6 Answers
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}
- 271,378
- 34
- 587
- 1,371
-
1Wow. 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
Try
f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]
- 394,356
- 18
- 477
- 896
-
Thanks! Nice generalizable solution with tight control of both the domain and range. – R. Peter DeLong Mar 05 '12 at 18:37
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].
-
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
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.)
- 20,779
- 2
- 64
- 121
-
Thanks. I have found the Evaluate[ ] function confusing… This helps. – R. Peter DeLong Mar 06 '12 at 21:49
This is probably the simplest way to define it:
(f[#]=31)&/@Range[12]
(f[#]=30)&/@{4,6,9,11}
f[2]=28
- 19,133
- 1
- 51
- 106
-
-
1
Scan[]would be a better thing to use here thanMap[]. – J. M.'s missing motivation May 04 '12 at 02:59 -
-
-
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]
- 19,133
- 1
- 51
- 106
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