Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

share|improve this question
1  
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
1  
Too subtle for me, I guess. How can it be humor without a smiley? :-) –  whuber Mar 6 '12 at 4:25
add comment

7 Answers

up vote 10 down vote accepted

Try

f[x_ /; MemberQ[Range@12, x]] := Switch[x, 2, 28, 4 | 6 | 9 | 11, 30, _, 31]
share|improve this answer
    
Thanks! Nice generalizable solution with tight control of both the domain and range. –  R. Peter DeLong Mar 5 '12 at 18:37
add comment

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

share|improve this answer
    
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
add comment

This is probably the simplest way to define it:

(f[#]=31)&/@Range[12]
(f[#]=30)&/@{4,6,9,11} 
f[2]=28
share|improve this answer
    
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
add comment

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

share|improve this answer
    
Thanks. I have found the Evaluate[ ] function confusing… This helps. –  R. Peter DeLong Mar 6 '12 at 21:49
add comment

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}
share|improve this answer
1  
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
add comment

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}]
share|improve this answer
add comment

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]
share|improve this answer
    
Where did you learn this? –  Mr.Wizard May 4 '12 at 17:01
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.