简体   繁体   中英

C++ equivalent to Python's cmp or Haskell's compare

Question:

Is there a C++ equivalent for Python's cmp or Haskell's compare ?

compare is like operator== and operator< in one. It returns LT , EQ , or GT . But it's twice as fast as calling both operator== and operator< because it does it in one pass.

More details:

At work, I often have structs that are used as keys for maps, for example:

struct RecordUsedAsAKey {
    int field_a;
    string field_b;
    vector<float> field_c;

    // operator< is needed for keys in maps.
    bool operator<(const RecordUsedAsAKey& other) const;
};

bool RecordUsedAsAKey::operator<(const RecordUsedAsAKey& other) const {
    if (field_a != other.field_a)
        return field_a < other.field_a;
    if (field_b != other.field_b)
        return field_b < other.field_b;
    return field_c < other.field_c;
}

One problem with RecordUsedAsAKey::operator< is that it's unnecessarily slow.

  • When the string::operator!= finds a different character, the program iterates over the equal characters again in the string::operator< , when it could have skipped those..
  • Same for the vector 's comparison.

If I had an equivalent to Haskell's compare , my comparison method would had been more efficient:

Ordering RecordUsedAsAKey::compare(const RecordUsedAsAKey& other) const {
    Ordering t;
    if ((t = field_a.compare(other.field_a)) != EQ)
        return t;
    if ((t = field_b.compare(other.field_b)) != EQ)
        return t;
    return field_c.compare(other.field_c);
}

This is more efficient because the string 's compare method does only one pass on the string.

Btw/mini-flame-war: in Haskell the whole code for the comparison would just be deriving Ord .

You can easily implement it yourself, as a free function.

#include <string>
#include <vector>
enum order {
    order_lt = -1,
    order_eq,
    order_gt
};

// General case, templated version.
template < typename T >
order compare(T left, T right) {
    if (left < right)
        return order_lt;
    if (left == right)
        return order_eq;
    return order_gt;
}

// Specialization
order compare(const std::string& left, const std::string& right) {
    return order(left.compare(right));
}
template < typename T >
order compare(const std::vector<T>& left, const std::vector<T>& right) {
     order o = compare(left.size(), right.size());
     if (o != order_eq)
         return o;
     for (size_t i = 0; i < left.size(); ++ i) {
         o = compare(left[i], right[i]);
         if (o != order_eq)
             return o;
     }
     return order_eq;
}

Note: I edited the code to include a templated version for the general case (work provided that the operator< and operator== are defined for the type). I also kept some specialization as it can improve run time on some type (mainly containers).

Edit: Using std::string::compare instead of strcmp .

Since map semantics are in term of operator< , and that in fact many operators implementations are in term of operator< , probably something only in term of it is better.

For instance:

template <typename T>
int compare(const T& x, const T& y)
{
    if (x < y) return -1;
    else if (y < x) return 1;
    else return 0;
}

or, better,

template <typename T, typename F>
int compare(const T& x, const T& y, F pred)
{
    if (pred(x, y)) return -1;
    else if (pred(y, x)) return 1;
    else return 0;
}

template <typename T>
int compare(const T& x, const T& y)
{
    return compare(x, y, std::less<T>());
}

so that you can use compare(k1, k2, mymap.key_comp()) if you need to.

After your program works, and you are convinced that compare is the bottleneck , you can specialize for the offending types. Do for instance

template <typename C, typename T, typename A>
int compare(const std::basic_string<C, T, A>& x,
            const std::basic_string<C, T, A>& y)
{
    return x.compare(y);
}

if you are worried about efficiency for string types.

If you are comparing sequences, you can use std::lexicographical_compare . However, you may want to reimplement it to handle the equality case, here is an optimized version for std::vector :

template <typename T, typename A, typename F>
int compare(const std::vector<T, A>& x,
            const std::vector<T, A>& y, F pred)
{
    std::vector<T, A>::const_iterator i = x.begin();
    std::vector<T, A>::const_iterator j = y.begin();

    while (i != x.end())
    {
        if (j == y.end()) return 1;
        if (pred(*i, *j)) return -1
        else if (pred(*j, *i)) return 1;

        ++i; ++j;
    }

    return j == y.end() ? 0 : -1;
}

simpler and more general version of Sylvain Defresne's answer:

template<typename T>
order compare(const T &left, const T &right) {
    if (left < right)
        return order_lt;
    else if (left == right)
        return order_eq;
    return order_gt;
}

The std::string already has a compare member function that does what you want.

For other sequences, like std::vector, there is a std::mismatch function in <algorithm> that scans two sequences side-by-side and returns iterators to the first two elements that differ. From there, you only have to figure out if these two elements are less than or greater than each other.

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