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 thestring::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开发者_如何学C(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.
精彩评论