Data analysis code (finds coincidences in three sets of data) I've written the following data analysis code (using just a couple lines from CERN's ROOT data analysis framework). It's designed to find coincidences in three sets of data (timestamps from an experiment for a given year) by pairing up each timestamp in one dataset with the closest possible timestamp in both the second and third datasets, then sorting those pairings by maxval - minval, and keeping only the closest &quot;unique&quot; coincidences (no two coincidences may share a timestamp, and each coincidence is a triplet, with one timestamp contributed by each dataset). The code works perfectly, however it's a bit messy and could probably use some cleaning up. Perhaps there are some clever tricks for simplifying some of this that I'm unaware of as well. Please do be very critical, I appreciate the feedback.
My code:
int binary_search(const vector&lt;double&gt; &amp;vec, const double key) {

    int high = vec.size();
    int low  = 0; 
    unsigned int mid;

    while(low &lt;= high){
        mid = low + (high-low)/2;

        if(vec[mid] == key) return mid;
        else if(vec[mid] &lt; key)  low = mid + 1;
        else high = mid - 1;
    }

    return mid;

}

void binary(){
    
    ROOT::RDataFrame statn1(&quot;D&quot;, &quot;./path/to/data&quot;);
    ROOT::RDataFrame statn2(&quot;D&quot;, &quot;./path/to/data&quot;);
    ROOT::RDataFrame statn3(&quot;D&quot;, &quot;./path/to/data&quot;);

    vector&lt;double&gt; vec2, vec3;
    statn2.Foreach([&amp;](double tstamp){vec2.push_back(tstamp);},{&quot;UNIX&quot;});
    statn3.Foreach([&amp;](double tstamp){vec3.push_back(tstamp);},{&quot;UNIX&quot;});
    
    vector&lt;vector&lt;double&gt; &gt; coincidences;
    statn1.Foreach([&amp;](double tstamp){},
    
        int res_i = binary_search(vec2, tstamp);

        double p = dabs(tstamp-vec2[res_i]); 
        double q = dabs(tstamp-vec2[res_i+1]); 
        double r = dabs(tstamp-vec2[res_i-1]);
        
        if(r&lt;q &amp;&amp; r&lt;p) --res_i;
        else if(q&lt;r &amp;&amp; q&lt;p) ++res_i;

        int res_j = binary_search(vec3, tstamp);

        p = dabs(tstamp-vec3[res_j]);
        q = dabs(tstamp-vec3[res_j+1]); 
        r = dabs(tstamp-vec3[res_j-1]);
        
        if(r&lt;q &amp;&amp; r&lt;p) --res_j;
        else if(q&lt;r &amp;&amp; q&lt;p) ++res_j;

        double first = tstamp-vec2[res_i]; 
        double second = tstamp-vec3[res_j]; 
        double third = vec2[res_i] - vec3[res_j]; 
        double fourth = std::min(std::min(first, second), third);

        coincidences.push_back({tstamp, vec2[res_i], vec3[res_j], first, second, third, fourth});
    
    {&quot;UNIX&quot;});
    
    std::sort(coincidences.begin(), coincidences.end(),
        [](const vector&lt;double&gt;&amp; A, const vector&lt;double&gt;&amp; B){
            return A[6] &lt; B[6];
    });
    
        std::set&lt;double&gt; cache;


    for(auto pair : coincidences){
        
        if(cache.find(pair[1]) == cache.end() &amp;&amp; cache.find(pair[2]) == cache.end())    {
            std::cout &lt;&lt; &quot;Coincidence found!\n&quot;;
            cache.insert(pair[1]); cache.insert(pair[2]);
        }
    }
}

EDIT: CERN's ROOT framework can be found at https://root.cern.ch
Undefined behavior
There's UB in case when empty vector passed to the binary_search.
unsigned int mid;

it is uninitialised and will pop whatever compiler think he want in release mode. To solve this issue it is require initialise value with some error value (usually it is ~0 but you can choose your own), or use std::optional (c++17) and handle error case in lambda.
probably it is not an issue if vectors guaranteed to be non-empty and there's at least one proper value inside them.
mix of signed and unsigned types
Return type of binary_search is int, but branches can be unsigned int.
As it is actually return position in vector best way to use special unsigned integral type macro size_t meant to be used like this. So final check will be like.
...
constexpr size_t ERR = ~0;
siz_t res_i = binary_search(vec2, tstamp);
if (ERR == res_i) return; // early exit from lambda
...

But that kind of handling raise question about how to handle res_i-- if binary search return position 0. It probably will return INT_MAX for according integral type, but that kind of subtraction is implementation defined behavior so result defined by compiler.
potential out of bounds
if binary search return 0 position access to -1 will crash your program.
double r = dabs(tstamp-vec2[res_i-1]);

tightly related to previous issues.
probably it is not an issue if found position never meant to be 0
unnecessary variable reusing
If your program don't meant to be run on some embed device this kind of optimization basically do nothing - compilers usually smart enough to get rid of it by themselves. Probably good idea create some small function and p,q,r manipulations in it. Also it will prevent chance to make copy-paste errors
size_t someRoutine(double tstamp, const auto&amp; vec, size_t res) {
  double p = dabs(tstamp-vec[res]); 
  double q = dabs(tstamp-vec[res+1]); 
  double r = dabs(tstamp-vec[res-1]);
        
  if(r&lt;q &amp;&amp; r&lt;p) return res-1;
  if(q&lt;r &amp;&amp; q&lt;p) return res+1;
  return res;
}
//...
res_i = someRoutine(tstamp, vec2, res_i);
res_j = someRoutine(tstamp, vec3, res_j);


structs clearer than vector
it is good idea to create a struct instead of using a vector - less memory print, clearer usage.
struct SomData{
  double tstamp, vec2,vec3, first, second, third, fourth;
};
//...
coincidences.push_back({tstamp, vec2[res_i], vec3[res_j], first, second, third, fourth});
// OR
// coincidences.emplace_back(tstamp, vec2[res_i], vec3[res_j], first, second, third, fourth); // construct struct in-place, 
//...
std::sort(coincidences.begin(), coincidences.end(),
   [](const SomData&amp; A, const SomData&gt;&amp; B){
            return A.fourth &lt; B.fourth;
});
///...
cache.insert(pair.vec2);cache.insert(pair.vec3);

no need to use find if you don't need an iterator
maps ands sets have convenient method count which represents amount of items found by the key. You just need to check whether its zero or one
if(cache.count(pair.vec2) == 0 &amp;&amp; cache.count(pair.vec3) == 0) {
  std::cout &lt;&lt; &quot;Coincidence found!\n&quot;;
  cache.insert(pair.vec2);
  cache.insert(pair.vec3);
}

cycle copy elements
probably just missed &amp;
for(const auto&amp; pair : coincidences){