Top-level solution based on recursion
I suggest a solution based on linked lists and recursion. It will not be blazing fast, but I think it is conceptually rather simple. Here is the code:
Clear[toLinkedList];
toLinkedList[lst_] := Fold[ll[#2, #1] &, ll[], Reverse@lst]
ClearAll[fillGaps];
fillGaps[dates_] :=
Block[{$IterationLimit = Infinity},
fillGaps[ll[], toLinkedList[dates]]];
fillGaps[accum_, ll[val : {d_, _}, tail : ll[{dn_, _}, _ll]]] :=
With[{nxt = DatePlus[d, 1]},
fillGaps[
If[nxt === dn, ll[accum, val], accum],
If[nxt === dn, tail, ll[val, ll[{nxt, 0}, tail]]]
]];
fillGaps[accum_, ll[val_, ll[]]] :=
Append[List @@ Flatten[accum, Infinity, ll], val];
The logic is straightforward: if we have two consecutive dates, we add the first to the linked list of accumulated results, and remove it from the remaining list of dates. If not, we insert an extra date adjacent to the first one, after the first one, and repeat. For those who are wondering why I have duplicate code with the comparisons inside If
statements, this is needed to make the function properly tail-recursive in Mathematica sense.
Here is the usage:
fillGaps[dateList]
(*
{{{2012, 1, 1}, 1}, {{2012, 1, 2}, 2}, {{2012, 1, 3}, 0}, {{2012, 1, 4}, 0},
{{2012, 1, 5}, 3}, {{2012, 1, 6}, 0}, {{2012, 1, 7}, 0}, {{2012, 1, 8}, 4}}
*)
My main message here is to not measure the simplicity necessarily by lines of code. This problem is a look-ahead type problem, and therefore linked lists and recursion seem a natural vehicle for solving it. OTOH, fitting it into the dominant Mathematica execution model where lists are operated on as a whole is of course possible, but IMO rather inelegant and indirect.
Java solution
Addressing your speed request in your edit, here is a Java solution (be sure to load the Java reloader first, along the steps described e.g. here:
JCompileLoad@"import java.util.*;
public class DateGapFiller{
public List<int[]> newDates = new ArrayList<int[]>();
public List<Double> newVals = new ArrayList<Double>();
public DateGapFiller(int[][] dates, double[] values){
if(dates.length==0) return;
Calendar c = Calendar.getInstance();
c.set(dates[0][0],dates[0][1]-1,dates[0][2]);
newVals.add(values[0]);
newDates.add(dates[0]);
for(int i = 1, ctr = 0; i<dates.length;ctr++){
c.add(Calendar.DATE,1);
int y = c.get(Calendar.YEAR);
int m = c.get(Calendar.MONTH)+1;
int d = c.get(Calendar.DAY_OF_MONTH);
int[] newDate = new int[]{y,m,d};
double newVal = 0;
if(dates[i][0]== y && dates[i][1] == m && dates[i][2] == d){
newDate = dates[i];
newVal = values[i];
i++;
}
newVals.add(newVal);
newDates.add(newDate);
}
}
}"
The top - level code is
ClearAll[fillDateGapsJ];
fillDateGapsJ[dates_List] :=
Block[{newDates, newVals, toArray},
JavaBlock[
With[{res = JavaNew["DateGapFiller", Sequence @@ Transpose[dates]]},
Transpose[res[#][toArray[]] & /@ {newDates, newVals}]
]]];
The usage is the same:
fillDateGapsJ[dateList]
The speed comparison:
dates =
NestList[
{DatePlus[#[[1]],RandomInteger[{1,5}]],RandomInteger[10]}&,
{{2000,1,1},1},
1000
];
(filled1= fillDateGaps[dates]);//AbsoluteTiming
(filled2= fillDateGapsJ[dates]);//AbsoluteTiming
filled2 == filled1
(*
{2.4482422,Null}
{0.0751953,Null}
True
*)
So you get about 30x speedup.
Remark
You may actually want to develop some custom data structure for "gapped dates" and a relevant custom plotting routine, as an alternative to all this. Which way to go depends on what you want to do with your data,of course.
getDateRange
function, what would be wrong withdaterange[firstd_List, lastd_List] := Array[DatePlus[firstd, # - 1] &, DateDifference[firstd, lastd] + 1]
. I don't have time to test extensively but I'm sure that will be much faster than anything usingNestWhileList
. $\endgroup$DateRange1[dtIni_List, dtFim_List] := NestWhileList[DatePlus[#, 1] &, dtIni, (# != dtFim) &]; DateRange2[firstd_List, lastd_List] := Array[DatePlus[firstd, # - 1] &, DateDifference[firstd, lastd] + 1] DateRange1[{2012, 1, 1}, {2012, 12, 31}]; // AbsoluteTiming DateRange2[{2012, 1, 1}, {2012, 12, 31}]; // AbsoluteTiming {0.2456, Null} {0.2477, Null}
$\endgroup$getDateRange[dtIni_,dtFim_]:=Part[DateList/@(Range[##,24*60^2]&@@AbsoluteTime/@{dtIni,dtFim}),All,{1,2,3}];
Tks Mr Wisard $\endgroup$