c++for-loopunordered-mapc++-chronounordered-multimap

Is the a better way to do an average of a unordered_multimap key?


I'm searching for get all value of one key in my unordered_multimap, which is actually stacking an int and a value who represent an execution time in nanosecs. I need to get all value to replace the multi keys by one key with an average.

I tried some code, this one is the most valuable actually :

std::unordered_multimap<int, std::chrono::nanoseconds> data;
std::chrono::nanoseconds average;
// *filling my map with value*
for (auto & pair : data){
  auto range = data.equal_range(pair.first);

  for_each (
    range.first,
    range.second,
    [](std::unordered_multimap<int, std::chrono::nanoseconds>::value_type& x){
      average = average + x.second;
    }
  );
  average = average / data.count(pair.first);
  data.erase(pair.first);
  data.insert({pair.first, average});
  }

The error i get error: 'average' is not captured : average = average + x.second;


Solution

  • Rather than std::for_each, use std::accumulate.

    Note that removing entries from a std::unordered_multimap within a ranged-for over it is undefined behaviour. It's safer to fill a different container.

    std::unordered_multimap<int, std::chrono::nanoseconds> data;
    // *filling my map with value*
    
    std::unordered_multimap<int, std::chrono::nanoseconds> new_data;
    for (auto it = data.begin(); it != data.end(); ++it){
      auto range = data.equal_range(it->first);
    
      auto average = std::accumulate (
        range.first,
        range.second,
        std::chrono::nanoseconds{0},
        [](auto sum, auto & x){
          return sum + x.second;
        }
      ) / std::distance(range.first, range.second);
    
      new_data.emplace(key, average);
    }
    data.swap(new_data);
    

    Alternatively, if you have C++17, std::transform_reduce.

      std::transform_reduce(
        range.first,
        range.second,
        std::chrono::nanoseconds{0},
        std::plus<>{},
        [](auto & x) { return x.second; }
      )