# Defining an integer-valued function on an integer domain

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.

-
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 5 '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 5 '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 5 '12 at 18:43
@whuber Well, there goes my attempt at humor for the day... – Brett Champion Mar 6 '12 at 1:27
Too subtle for me, I guess. How can it be humor without a smiley? :-) – whuber Mar 6 '12 at 4:25

Try

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

-
 Thanks! Nice generalizable solution with tight control of both the domain and range. – R. Peter DeLong Mar 5 '12 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}

-
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 6 '12 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].

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

-
 Thanks. I have found the Evaluate[ ] function confusing… This helps. – R. Peter DeLong Mar 6 '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

-
 Another great approach. I love learning these tricks. Thanks! – R. Peter DeLong Mar 5 '12 at 20:06 Scan[] would be a better thing to use here than Map[]. – J. M.♦ May 4 '12 at 2:59 @J.M.: Better in what way? – celtschk May 4 '12 at 7:13 See the discussion under the comments here. – J. M.♦ May 4 '12 at 7:19 @J.M.: I see, thanks. – celtschk May 4 '12 at 7:27

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]

-
 Where did you learn this? – Mr.Wizard♦ May 4 '12 at 17:01

Here's a variation of Brett's and celtschk's answers, which uses Scan[] instead of Map[]:

i = 0;
Scan[(f[++i] = #) &, {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}]

-