简体   繁体   中英

How to compare all items in std::map?

I'd like to compare all values in my std::map with each other.

I'm stuck with: for linear containers, like vector, I'd loop over indices i=1; v[i].isUniform(v[i-1]) i=1; v[i].isUniform(v[i-1]) . But I can't do this with maps. I'm looking forward to hear clever ideas.

Here is some pseudocode of what I want to accomplish:

class MyData
{
public:
    bool isUniform(const MyData& other) const
    {
        return this->speed == other.speed && this->ban == other.ban;
    }

private:
    bool ban;
    int  speed;
}

std::map<int, MyData> myMap;

bool allUniform = true;
for(item_1, item_2 : myMap) // how to implement this?
{
    if(!item_1.isUniform(item_2))
    {
        allUniform = false;
    }
}

What is the most elegant (readable and efficient) way to do that?

You can use std::all_of for this with a lambda. That would look like

bool allUniform = std::all_of(std::next(myMap.begin()), 
                              myMap.end(), 
                              [&myMap](const auto& pair)
                              { return myMap.begin()->second.isUniform(pair.second); });

This goes from [1, N) and calls isUniform againt each of those elements against the first. all_of also short circuts so as soon as you have a non-uniform result it will end.

Is isUniform() transitive? That is, does it follow this rule for any 3 MyData objects:

 isUniform(A, B) && isUniform(B, C) == isUniform(A, C)

If so, then you only need O(n) comparisons. Here is my test code:

#include <iostream>
#include <map>

using namespace std;

class MyData {
public:
    int value;
    MyData(int _value) : value(_value) {}

    bool isUniform(const MyData &obj) const { return value == obj.value; }

    static void checkMap(map<int, MyData> myMap) {
         bool allUniform = true;
         MyData * firstItem = nullptr;

         for (auto it = myMap.begin(); allUniform && it != myMap.end(); ++it) {
             if (firstItem == nullptr) {
                  firstItem = &it->second;
             }
             else {
                  allUniform = firstItem->isUniform(it->second);
             }
         }

         cout << "All Uniform: " << (allUniform ? "Yes" : "No") << endl;
    }
};

int main(int, char **) {
    map<int, MyData> map1;
    map<int, MyData> map2;
    MyData a1(1);
    MyData b1(1);
    MyData b2(2);
    MyData c1(1);

    // Should be uniform
    map1.insert(std::pair<int, MyData>(1, a1));
    map1.insert(std::pair<int, MyData>(2, b1));
    map1.insert(std::pair<int, MyData>(3, c1));
    MyData::checkMap(map1);

    // Should not be uniform
    map2.insert(std::pair<int, MyData>(1, a1));
    map2.insert(std::pair<int, MyData>(2, b2));
    map2.insert(std::pair<int, MyData>(3, c1));
    MyData::checkMap(map2);

    return 0;
}

With this output:

$ g++ -std=c++0x Foo.cpp -o Foo && Foo
All Uniform: Yes
All Uniform: No
for(item_1, item_2 : myMap) // how to implement this?

But if they're all equal to each other (assuming isUniform is transitive and symmetric), that's the same as saying they're all equal to the first element. So, you can just compare each element to the first.

Naively, we get:

bool allUniform(std::map<int, MyData> const &m)
{
    if (m.empty()) return true; // or whatever makes sense for you

    auto first = m.begin();
    for (auto current = m.begin(); ++current != m.end(); )
    {
        if (!first->second.isUniform(current->second))
        {
            return false;
        }
    }
    return true;
}

You can simplify this if you don't mind comparing the first element to itself, because really most of the complexity is just avoiding that.

It'd probably be even better to usestd::adjacent_find as below, or the std::all_of solution in the other answer.

bool allUniform(std::map<int, MyData> const &m)
{
    auto first_non_uniform =
        std::adjacent_find(
            m.begin(), m.end(),
            [](auto left, auto right)
            {
                return !(left->second.isUniform(right->second));
            }
           );

    return first_non_uniform == m.end();
}

You can use the standard algorithm std::adjacent_find .

For example

#include <iostream>
#include <iomanip>
#include <map>
#include <iterator>
#include<algorithm>

class MyData
{
public:
    MyData( bool ban, int speed ) : ban( ban ), speed( speed )
    {
    }

    bool isUniform(const MyData& other) const
    {
        return this->speed == other.speed && this->ban == other.ban;
    }

private:
    bool ban;
    int  speed;
};



int main() 
{
    std::map<int, MyData> myMap =
    {
        { 1, { true, 100 } }, { 2, { true, 100 } }, { 3, { true, 100 } }    
    };

    auto it = std::adjacent_find( std::begin( myMap ), std::end( myMap ),
                                  []( const auto &p1, const auto &p2 ) 
                                  {
                                    return not p1.second.isUniform( p2.second );
                                  } );

    bool allUniform = it == std::end( myMap );

    std::cout << std::boolalpha << allUniform << '\n';

    return 0;
}

The program output is

true

Compare all elements in a collection to the first one:

template<class M, class F>
bool allEqual( M const& m, F&& f ) {
  using std::begin; using std::end;
  auto b = begin(m);
  auto e = end(m);
  if (b == e) return true;
  for (auto it = std::next(b); it != e; ++it)
    if (!f( *b, *it))
      return false;
  return true;
}

compare all elements pairwise:

template<class M, class F>
bool pairwiseCompare( M const& m, F&& f ) {
  for (auto&& a:m)
    for (auto&& b:m)
    {
      if (std::addressof(a) == std::addressof(b))
        continue;
      if (!f(a,b))
        return false;
    }
  return true;
}

this won't work on pseudo-containers like std::vector<bool> ; well, it sort of will, but it will wastefully self-compare elements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM