首页 > 解决方案 > 对象集的排序不正确

问题描述

当我打印整套时,结果是未排序的并且它包含一个副本。该对象Person有一个姓氏、一个姓氏和出生年份(这三个都是字符串)。我首先按出生年份排序,然后按姓氏,然后按姓氏。本质上,没有相同的人(但即使是这种情况,也应该在他们插入时将其删除set)。

更具体地说,我创建了一组这样的人:

std::set <Person> greatUncles; 

并像这样插入它们:

greatUncles.insert(Person("bla", "bla", "1900"));

这是课堂上的基本内容Person

class Person {
public:
  //...

  Person(std::string s, std::string f, std::string y)
    :surname(s), familyname(f), yearOfBirth(y)
  {
  }

  //...

  std::string getSurname() const {
    return surname;
  }

  std::string getFamilyname() const {
    return familyname;
  }

  std::string getYearOfBirth() const {
    return yearOfBirth;
  }

private:
  std::string surname;
  std::string familyname;
  std::string yearOfBirth;
};

//to print the set, overload the '<<' operator
std::ostream &operator<<(std::ostream &o, const Person &person) {
  o << person.getSurname() << " "
    << person.getFamilyname() << " "
    << person.getYearOfBirth() << std::endl;
  return o;
}

//to order the set, overload the '<' operator
bool operator< (Person const &p1, Person const &p2) {
  int compareYearOfBirth = p1.getYearOfBirth().compare(p2.getYearOfBirth());

  if (compareYearOfBirth == 0) {
    int compareFamilyname = p1.getFamilyname().compare(p2.getFamilyname());
    if (compareFamilyname == 0) {
      return p1.getSurname().compare(p2.getSurname());
    } else
      return compareFamilyname;
  } else
    return compareYearOfBirth;
}

这是我打印叔叔的方式:

void printGreatUncles(std::set <Person> &greatUncles) {
    std::ofstream outputFile;
    outputFile.open("greatuncle.dat");

    if (outputFile.is_open()) {
      for(Person const & person:greatUncles) {
        outputFile << person;
      }
      outputFile.close();
    }
  }

现在在某种情况下的输出应该是这样的(按年份排序):

Sebastian Furtweger 1942
Nikolaus Furtweger 1951
Archibald Furtweger 1967

但它看起来像这样:

Archibald Furtweger 1967
Sebastian Furtweger 1942
Nikolaus Furtweger 1951
Archibald Furtweger 1967

我无法为我的生活弄清楚我做错了什么(事情)。

标签: c++c++11objectsetoverloading

解决方案


std::set requires the comparator provides a strict weak ordering. Part of that is if a < b == true then b < a == false but you don't have this. Lets imagine the birth year and the family names are the same, and only the surnames are different. In you example you would return some positive or negative number which is converted to true since only 0 is false. If you run the check backwards then you get the opposite value in the integer, but it still results in true.

To fix this C++11 offers std::tie that you can use to build a std::tuple of the members and its operator < is built to do the right thing. That makes you code look like

bool operator< (Person const &p1, Person const &p2) {
  return std::tie(p1.getYearOfBirth(), p1.getFamilyname(), p1.getSurname()) < 
         std::tie(p2.getYearOfBirth(), p2.getFamilyname(), p2.getSurname());
}

If you ever want to do this going forward and can use C++20 then you can add to Person

auto operator<=>(const Person&) const = default;

and that will automatically give you operators ==, !=, <, <=, >, and >= for Person and they will "do the right thing" as long as you want all members compared in the order they are defined in the class.


推荐阅读