开发者

What is the best way to implement operator<?

Sorry if this is a stupid question, but it's something that I'm curious about.

I am overloading the less-than operator for my sort algorithm based on last name, first name, middle name. I realize there is not a right or wrong here, but I'm curious as to which style is written better or preferred among fellow programmers.

bool CPerson::operator<(const CPerson& key) const
{
    if (m_Last < key.m_Last)
        || ( (m_Last == key.m_Last) && (m_First < key.m_First) )
        || ( (m_Last == key.m_Last) && (m_First == key.m_First) && (m_Middle < key.m_Middle) )
        return true;
    return false;
}

or

bool CPerson::operator<(const CPerson& key) const
{
    if (m_Last < key.m_Last)
        return true;
    else if ( (m_Last == key.m_Last) && (m_First < key.m_First) )
        return true;
    else if ( (m_Last == key.m_Last) && (m_First == key.m_First) && (m_Middle < key.m_Middle) )
        return true;
    else
        return false;
}

or

bool CPerson::operator<(const CPerson& key) const
{
    if (m_Last < key.m_Last)
        return true;
开发者_Go百科
    if (m_Last == key.m_Last)
        if (m_First < key.m_First)
            return true;

    if (m_Last == key.m_Last)
        if (m_First == key.m_First)
            if (m_Middle < key.m_Middle)
                return true;

    return false;
}


I prefer:

bool CPerson::operator<(const CPerson& key) const
{
    if (m_Last == key.m_Last) {
        if (m_First == key.m_First) {
            return m_Middle < key.m_Middle;
        }
        return m_First < key.m_First;
    }
    return m_Last < key.mLast;
}

Nice and systematic, and it is obvious how new members can be added.


Because these are strings, the repeated comparison may be needlessly inefficient. Following David Hamman's suggestion, here is a version which only does the comparisons once per string (at most):

bool CPerson::operator<(const CPerson& key) const
{
    int last(m_Last.compare(key.m_Last));
    if (last == 0) {
        int first(m_First.compare(key.m_First));
        if (first == 0) {
            return m_Middle < key.m_Middle;
        }
        return first < 0;
    }
    return last < 0;
}


All of your implementations are essentially the same and they are all wrong by any reasonable definition of sort order for people's names. Your algorithm will place Jonathan Abbott Zyzzyk ahead of Jonathan Zuriel Aaron.

What you want is person A's name is less than person B's name if:

  • The last name of person A is less than the last name of person B or
  • The two have the same last name and
    • The first name of person A is less than the first name of person B or
    • The two have the same first name and the middle name of person A is less than the middle name of person B.

Whether you implement this as a single boolean expression versus a staged if/else sequence is a bit of personal preference. My preference is the single boolean expression; to me that logical expression is clearer than a cluttered if/else sequence. But apparently I'm weird. Most people prefer the if/else construct.

Edit, per request
As a single boolean expression,

bool Person::operator< (const Person& other) const {
  return (last_name < other.last_name) ||
         ((last_name == other.last_name) &&
          ((first_name < other.first_name) ||
           ((first_name == other.first_name) &&
            (middle_name < other.middle_name))));
}


I find the first one the most difficult to read of the three (although none of them are too difficult) and the first one has unnecessary parentheses. The second one is my personal preference, because the third one seems too long and verbose.

This really is subjective though.


I normally write a comparison function roughly like this:

bool whatever::operator<(whatever const &other) { 

    if (key1 < other.key1)
        return true;

    if (other.key1 < key1)
        return false;

    // compare the second key item because the first ones were equal.
    if (key2 < other.key2) 
        return true;

    if (other.key2 < key2)
       return false;

    // repeat for as many keys as needed

    // for the last key item, we can skip the second comparison:
    if (keyN < other.keyN)
        return true;

    return false; // other.keyN >= keyN.
}


Along a slightly different vein, all of the solutions (including my first answer) tend to compare names twice, once for less than and again for equality. Since sort is at best an N*logN algorithm, efficiency can be quite important when sorting a big list of names, and these duplicative comparisons are rather inefficient. The string::compare method provides a mechanism for bypassing this problem:

bool Person::operator< (const Person& other) const {
  int cmp = last_name.compare (other.last_name);
  if (cmp < 0) {
     return true;
  } else if (cmp == 0) {
    cmp = first_name.compare (other.first_name);
    if (cmp < 0) {
      return true;
    } else if (cmp == 0) {
      cmp = middle_name.compare (other.middle_name);
      if (cmp < 0) {
        return true;
      }
    }
  }
  return false;
}

Edit, per request
Elided.

A boolean version of the above will either result in undefined behavior or will use multiple embedded uses of the ternary operator. It is ugly even given my penchant for hairy boolean expressions. Sorry, Mankarse.


I like to reduce this to tuples, which already implement this kind of lexicographical ordering. For example, if you have boost, you can write:

bool Person::operator< (const Person& Rhs) const
{
  return boost::tie(m_Last, m_First, m_Middle) < boost::tie(Rhs.m_Last, Rhs.m_First, Rhs.m_Middle);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜