# 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=31, f=28, f=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 = 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 = 31; h = 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 -> 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,
Part[
DatePlus[{year - 1, 12, 31}, {#, "Month"}] & /@ Range,
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


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], {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 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
(f[#]=30)&/@{4,6,9,11}
f=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. will be back soon 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. will be back soon 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