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.
6 Answers
Try
f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]
-
$\begingroup$ Thanks! Nice generalizable solution with tight control of both the domain and range. $\endgroup$ Commented Mar 5, 2012 at 18:37
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}
-
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
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]
.
-
$\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
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.)
-
$\begingroup$ Thanks. I have found the Evaluate[ ] function confusing… This helps. $\endgroup$ Commented Mar 6, 2012 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
-
$\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 thanMap[]
. $\endgroup$ Commented May 4, 2012 at 2: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]
f[m,d] = <hours per day>
. Who'd want to type all 365(6) entries in by hand? $\endgroup$