c++ 带有std::optional的向量的std::minmax_element比较器不正确

vwoqyblh  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(222)

我在std::optionals的结构中有一个结构,我试图找到结构中的最小值和最大值。然而,使用std::minmax_element似乎不准确,我不得不拆分比较函数,转而使用std::min_element和std::max_element。有没有一种方法可以让我仍然使用std::minmax_element并且更干净?

  1. #include <iostream>
  2. #include <vector>
  3. #include <optional>
  4. #include <algorithm>
  5. struct AE
  6. {
  7. double A;
  8. double E;
  9. };
  10. struct HM
  11. {
  12. std::optional<AE> H;
  13. std::optional<AE> M;
  14. };
  15. bool CompareHM(const HM& a_, const HM& b_)
  16. {
  17. // If both H fields have a value, compare them by A
  18. if (a_.H.has_value() && b_.H.has_value())
  19. {
  20. return a_.H->A < b_.H->A;
  21. }
  22. // If only one H field has a value, set to false
  23. else
  24. {
  25. return false;
  26. }
  27. }
  28. bool CompareHMMax(const HM& a_, const HM& b_)
  29. {
  30. // If both H fields have a value, compare them by A
  31. if (a_.H.has_value() && b_.H.has_value())
  32. {
  33. return a_.H->A < b_.H->A;
  34. }
  35. // If only one H field has a value, consider it larger
  36. else if (a_.H.has_value())
  37. {
  38. return false;
  39. }
  40. // If both H fields are empty, or if just b has a value
  41. else {
  42. return false;
  43. }
  44. }
  45. bool CompareHMMin(const HM& a_, const HM& b_)
  46. {
  47. // If both H fields have a value, compare them by A
  48. if (a_.H.has_value() && b_.H.has_value())
  49. {
  50. return a_.H->A < b_.H->A;
  51. }
  52. // If only one H field has a value, consider it smaller
  53. else if (a_.H.has_value())
  54. {
  55. return true;
  56. }
  57. // If both H fields are empty, or if just b has a value
  58. else
  59. {
  60. return false;
  61. }
  62. }
  63. int main()
  64. {
  65. // Create a vector of HM structs with some data
  66. std::vector<HM> vec = {
  67. HM{AE{1.8, 4.5}, AE{7.2, 4.1}},
  68. HM{AE{2.3, 3.4}, std::nullopt},
  69. HM{std::nullopt, AE{6.7, 8.9}},
  70. HM{AE{0.9, 2.1}, std::nullopt},
  71. HM{AE{1.8, 4.2}, AE{7.6, 9.0}},
  72. HM{std::nullopt, AE{0.7, 13.5}},
  73. HM{std::nullopt, AE{0.5, 19.3}},
  74. HM{AE{2.1, 4.2}, std::nullopt},
  75. };
  76. auto minmax = std::minmax_element(vec.begin(), vec.end(), CompareHM);
  77. auto min = std::min_element(vec.begin(), vec.end(), CompareHMMin);
  78. auto max = std::max_element(vec.begin(), vec.end(), CompareHMMax);
  79. // Print the results
  80. std::cout << "Correct Min A value: " << min->H->A << "\n";
  81. std::cout << "Correct Max A value: " << max->H->A << "\n";
  82. std::cout << "Incorrect Min A value using minmax_element: " << minmax.first->H->A << "\n";
  83. std::cout << "Incorrect Max A value using minmax_element: " << minmax.second->H->A << "\n";
  84. return 0;
  85. }

输出:

  1. Correct Min A value: 0.9
  2. Correct Max A value: 2.3
  3. Incorrect Min A value using minmax_element: 1.8
  4. Incorrect Max A value using minmax_element: 2.1
kpbwa7wx

kpbwa7wx1#

CompareHM不会导致 * 严格弱序 *,这就是为什么std::minmax_element会给出无意义的结果。
严格弱序需要是传递的,而不可比较的元素打破了这一点:

  1. a < std::nullopt && std::nullopt < b => a < b

这意味着对于 * 严格弱序 * 必须为真,但当std::nullopt是不可比较的时显然不是。
不可比性需要是一种传递关系,即

  1. a incomparable_to std::nullopt && b incomparable_to => a incomparable_to b

但如果ab相当,情况显然不是这样。顺便说一下,这也是为什么你不能对包含NaN的float s的范围进行排序的原因(参见Is NaN a valid key value for associative containers?)。
MinMax关系不同,我们不能简单地将std::nullopt视为正/负无穷大值。

C++17或更早版本

在旧的C++标准中,可以使用remove-erase习惯用法中的std::remove_if就地消除std::nullopt元素:

  1. vec.erase(std::remove_if([](const HM& h) {
  2. return !h.has_value();
  3. }, vec.end());
  4. auto minmax = std::minmax_element(vec);
  5. std::cout << minmax.first->H->A << ' ' << minmax.second->H->A << '\n';

实际上,如果您的目标只是找到最小和最大元素,那么只使用std::remove_if就足够了。显然,总是可以选择分别使用std::min_elementstd::max_element

C++20

我们可以使用std::ranges::filter_view来查看没有任何std::nullopt元素的范围,允许std::ranges::minmax_element正确工作。

  1. auto filter = std::ranges::filter_view(vec, [](const HM& h) {
  2. return h.H.has_value();
  3. });
  4. auto minmax = std::ranges::minmax_element(filter, CompareHM);
  5. std::cout << minmax.min->H->A << ' ' << minmax.max->H->A << '\n';
展开查看全部
zzzyeukh

zzzyeukh2#

一个大问题是,为了使用std::minmax_element,你需要决定一个无值的optional应该大于还是小于一个有值的可选值。不可能两者都是一个简单的解决方案是排除H是无值的HM s,首先通过划分vector
您也可以使用defaultoperator<=>来简化此操作。对于optional

  • 仅当lhs和rhs都包含值时,才比较包含的值(使用相应的运算符T)。否则
  • 当且仅当LHS和RHS都不包含值时,认为LHS等于RHS。
  • 当且仅当rhs包含值而lhs不包含值时,lhs被认为小于rhs。

这对于需要 * 严格的周排序 * 的算法很好,所以:

  1. struct AE {
  2. double A;
  3. double E;
  4. auto operator<=>(const AE& other) const = default;
  5. };
  6. struct HM {
  7. std::optional<AE> H;
  8. std::optional<AE> M;
  9. auto operator<=>(const HM&) const = default;
  10. };

然后将vector分区,以将HM与无值H放在vector的最后:

  1. auto pp = std::stable_partition(vec.begin(), vec.end(), [](auto&& hm) {
  2. return hm.H.has_value();
  3. });

然后minmax_element可以完成它的任务:

  1. auto [min, max] = std::minmax_element(vec.begin(), pp);

Demo
如果你不想对vector进行分区,你也可以使用ranges::filter_view,比如Jan showed,只是你不需要自定义比较器。
在C++17和更早版本中,您可以为两个类定义operator<,以便简化工作。仍然不需要复杂的定制比较器:

  1. struct AE {
  2. double A;
  3. double E;
  4. bool operator<(const AE& other) const {
  5. return std::tie(A, E) < std::tie(other.A, other.E);
  6. }
  7. };
  8. struct HM {
  9. std::optional<AE> H;
  10. std::optional<AE> M;
  11. bool operator<(const HM& other) const {
  12. return std::tie(H, M) < std::tie(other.H, other.M);
  13. }
  14. };

分区和调用minmax_element的工作原理与上面的相同。

展开查看全部

相关问题