5
$\begingroup$

I am working on a project that involves identifying runs of dates that fall within five business days of each other. Given a list of dates in inDates, the following code does what I want:

Gather[inDates, DatePlus[#1, {5, "BusinessDay"}] > #2 &]

For input longer than a few dates, though, it's insanely slow. Consider the following 139-member input:

inDates={DateObject[{2015, 7, 31}], DateObject[{2015, 11, 4}], DateObject[{2016, 1, 8}], DateObject[{2016, 6, 17}], DateObject[{2016, 11, 21}], DateObject[{2017, 2, 15}], DateObject[{2017, 2, 16}], DateObject[{2017, 2, 17}], DateObject[{2017, 2, 21}], DateObject[{2017, 2, 22}], DateObject[{2017, 2, 23}], DateObject[{2017, 2, 24}], DateObject[{2017, 2, 27}], DateObject[{2017, 2, 28}], DateObject[{2017, 3, 1}], DateObject[{2017, 3, 2}], DateObject[{2017, 3, 3}], DateObject[{2017, 3, 6}], DateObject[{2017, 3, 7}], DateObject[{2017, 3, 8}], DateObject[{2017, 3, 9}], DateObject[{2017, 3, 10}], DateObject[{2017, 3, 13}], DateObject[{2017, 3, 14}], DateObject[{2017, 3, 15}], DateObject[{2017, 3, 16}], DateObject[{2017, 3, 17}], DateObject[{2017, 3, 20}], DateObject[{2017, 3, 21}], DateObject[{2017, 3, 22}], DateObject[{2017, 3, 23}], DateObject[{2017, 3, 24}], DateObject[{2017, 3, 27}], DateObject[{2017, 3, 28}], DateObject[{2017, 3, 29}], DateObject[{2017, 3, 30}], DateObject[{2017, 3, 31}], DateObject[{2017, 4, 3}], DateObject[{2017, 4, 4}], DateObject[{2017, 4, 5}], DateObject[{2017, 4, 6}], DateObject[{2017, 4, 7}], DateObject[{2017, 4, 10}], DateObject[{2017, 4, 11}], DateObject[{2017, 4, 12}], DateObject[{2017, 4, 13}], DateObject[{2017, 4, 17}], DateObject[{2017, 4, 18}], DateObject[{2017, 4, 19}], DateObject[{2017, 4, 20}], DateObject[{2017, 4, 21}], DateObject[{2017, 4, 24}], DateObject[{2017, 4, 25}], DateObject[{2017, 4, 26}], DateObject[{2017, 4, 27}], DateObject[{2017, 4, 28}], DateObject[{2017, 5, 1}], DateObject[{2017, 5, 2}], DateObject[{2017, 5, 3}], DateObject[{2017, 5, 4}], DateObject[{2017, 5, 5}], DateObject[{2017, 5, 8}], DateObject[{2017, 5, 9}], DateObject[{2017, 5, 10}], DateObject[{2017, 5, 11}], DateObject[{2017, 5, 12}], DateObject[{2017, 5, 15}], DateObject[{2017, 5, 16}], DateObject[{2017, 5, 17}], DateObject[{2017, 5, 18}], DateObject[{2017, 5, 19}], DateObject[{2017, 5, 22}], DateObject[{2017, 5, 23}], DateObject[{2017, 5, 24}], DateObject[{2017, 5, 25}], DateObject[{2017, 5, 26}], DateObject[{2017, 5, 30}], DateObject[{2017, 5, 31}], DateObject[{2017, 6, 1}], DateObject[{2017, 6, 2}], DateObject[{2017, 6, 5}], DateObject[{2017, 6, 6}], DateObject[{2017, 6, 7}], DateObject[{2017, 6, 8}], DateObject[{2017, 6, 9}], DateObject[{2017, 6, 12}], DateObject[{2017, 6, 13}], DateObject[{2017, 6, 14}], DateObject[{2017, 6, 15}], DateObject[{2017, 6, 16}], DateObject[{2017, 6, 19}], DateObject[{2017, 6, 20}], DateObject[{2017, 6, 21}], DateObject[{2017, 6, 22}], DateObject[{2017, 6, 23}], DateObject[{2017, 6, 26}], DateObject[{2017, 6, 27}], DateObject[{2017, 6, 28}], DateObject[{2017, 6, 29}], DateObject[{2017, 6, 30}], DateObject[{2017, 7, 3}], DateObject[{2017, 7, 5}], DateObject[{2017, 7, 6}], DateObject[{2017, 7, 7}], DateObject[{2017, 7, 10}], DateObject[{2017, 7, 11}], DateObject[{2017, 7, 12}], DateObject[{2017, 7, 13}], DateObject[{2017, 7, 14}], DateObject[{2017, 7, 17}], DateObject[{2017, 7, 18}], DateObject[{2017, 7, 19}], DateObject[{2017, 7, 20}], DateObject[{2017, 7, 21}], DateObject[{2017, 7, 24}], DateObject[{2017, 7, 25}], DateObject[{2017, 7, 26}], DateObject[{2017, 7, 27}], DateObject[{2017, 7, 28}], DateObject[{2017, 7, 31}], DateObject[{2017, 8, 1}], DateObject[{2017, 8, 2}], DateObject[{2017, 8, 3}], DateObject[{2017, 8, 4}], DateObject[{2017, 8, 7}], DateObject[{2017, 8, 8}], DateObject[{2017, 8, 9}], DateObject[{2017, 8, 10}], DateObject[{2017, 8, 11}], DateObject[{2017, 8, 14}], DateObject[{2017, 8, 15}], DateObject[{2017, 8, 16}], DateObject[{2017, 8, 17}], DateObject[{2017, 8, 18}], DateObject[{2017, 8, 21}], DateObject[{2017, 8, 22}], DateObject[{2017, 8, 23}], DateObject[{2017, 8, 24}], DateObject[{2017, 8, 25}]};

On my desktop machine, this takes almost two minutes to process, and I have thousands of such inputs to deal with so it's not acceptable. I can save 97% of my run time by ditching the BusinessDay requirement and instead running

Gather[inDates, DatePlus[#1, 5] > #2 &]

But this really isn't what I want. Is there some way to radically speed up this operation while preserving the BusinessDay math?

$\endgroup$
  • 2
    $\begingroup$ You could sort the dates and use Split instead of Gather. This way only neighbouring list elements need to be compared, so you get O(1) complexity instead of O(2). There's a fundamental problem with what you're doing though: "within 5 business days of each other" is not an equivalence relation (it's not transitive) ... $\endgroup$ – Szabolcs Sep 13 '17 at 15:09
  • $\begingroup$ @Szabolcs For those of us with small minds, explain in more detail what the fundamental problem is? $\endgroup$ – Michael Stern Sep 13 '17 at 15:13
  • 2
    $\begingroup$ Maybe I misunderstood something ... but if the question were to break a list of numbers into sets so that numbers are within a distance of two, then we can do {1,2,3,7,8,9} -> {{1,2,3}, {7,8,9}}. That's fine. But what if we have {1,2,3,4,5}? 1, 3 are within a distance of 2 and so are 3, 5. But 1, 5 aren't. So how do we break up the list? $\endgroup$ – Szabolcs Sep 13 '17 at 15:15
  • 2
    $\begingroup$ The documentation of Gather implies that it expects an equivalence relation as its second argument (i.e. an equality test that does not lead to contradiction like above: a==b` and b==c but a!=c). I would not trust it when passing it a comparison that is not an equivalence relation. Of course in practical situations it often makes sense to work with such flawed relations—but you need to be aware of potential problems. Technically even == isn't an equivalence relation in Mathematica because it works with tolerances. $\endgroup$ – Szabolcs Sep 13 '17 at 15:19
  • 1
    $\begingroup$ @Szabolcs You win. Gather with BusinessDays → 114 seconds. Split with BusinessDays → 6 seconds. Split with ordinary days → 0.2 seconds. I appreciate you don't need the stackexchange points, but if you put this suggestion as an answer I will accept it, allowing others to see the problem has been solved. $\endgroup$ – Michael Stern Sep 13 '17 at 15:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.