提问人:Brinck 提问时间:6/20/2023 更新时间:6/20/2023 访问量:129
std::minmax_element 带有 std::optional 的向量的比较器不正确
std::minmax_element comparer for a vector with std::optional is incorrect
问:
我在 std::optionals 的结构中有一个结构,我正在尝试在结构中找到最小值和最大值。但是,使用 std::minmax_element 似乎不准确,我不得不拆分比较函数并改用 std::min_element 和 std::max_element。有没有办法解决这个问题,让我仍然可以使用 std::minmax_element 并且更干净?
#include <iostream>
#include <vector>
#include <optional>
#include <algorithm>
struct AE
{
double A;
double E;
};
struct HM
{
std::optional<AE> H;
std::optional<AE> M;
};
bool CompareHM(const HM& a_, const HM& b_)
{
// If both H fields have a value, compare them by A
if (a_.H.has_value() && b_.H.has_value())
{
return a_.H->A < b_.H->A;
}
// If only one H field has a value, set to false
else
{
return false;
}
}
bool CompareHMMax(const HM& a_, const HM& b_)
{
// If both H fields have a value, compare them by A
if (a_.H.has_value() && b_.H.has_value())
{
return a_.H->A < b_.H->A;
}
// If only one H field has a value, consider it larger
else if (a_.H.has_value())
{
return false;
}
// If both H fields are empty, or if just b has a value
else {
return false;
}
}
bool CompareHMMin(const HM& a_, const HM& b_)
{
// If both H fields have a value, compare them by A
if (a_.H.has_value() && b_.H.has_value())
{
return a_.H->A < b_.H->A;
}
// If only one H field has a value, consider it smaller
else if (a_.H.has_value())
{
return true;
}
// If both H fields are empty, or if just b has a value
else
{
return false;
}
}
int main()
{
// Create a vector of HM structs with some data
std::vector<HM> vec = {
HM{AE{1.8, 4.5}, AE{7.2, 4.1}},
HM{AE{2.3, 3.4}, std::nullopt},
HM{std::nullopt, AE{6.7, 8.9}},
HM{AE{0.9, 2.1}, std::nullopt},
HM{AE{1.8, 4.2}, AE{7.6, 9.0}},
HM{std::nullopt, AE{0.7, 13.5}},
HM{std::nullopt, AE{0.5, 19.3}},
HM{AE{2.1, 4.2}, std::nullopt},
};
auto minmax = std::minmax_element(vec.begin(), vec.end(), CompareHM);
auto min = std::min_element(vec.begin(), vec.end(), CompareHMMin);
auto max = std::max_element(vec.begin(), vec.end(), CompareHMMax);
// Print the results
std::cout << "Correct Min A value: " << min->H->A << "\n";
std::cout << "Correct Max A value: " << max->H->A << "\n";
std::cout << "Incorrect Min A value using minmax_element: " << minmax.first->H->A << "\n";
std::cout << "Incorrect Max A value using minmax_element: " << minmax.second->H->A << "\n";
return 0;
}
输出:
Correct Min A value: 0.9
Correct Max A value: 2.3
Incorrect Min A value using minmax_element: 1.8
Incorrect Max A value using minmax_element: 2.1
答:
CompareHM
不会诱发严格的弱排序,这就是为什么会给你无意义的结果。std::minmax_element
严格的弱排序需要是传递的,而无与伦比的元素打破了这一点:
a < std::nullopt && std::nullopt < b => a < b
对于严格的弱排序来说,这种含义必须是正确的,但显然不是不可比拟的。std::nullopt
不可比性需要是一种传递关系,即
a incomparable_to std::nullopt && b incomparable_to => a incomparable_to b
但是,如果与 .
顺便说一句,这也是为什么不能对包含 NaN 的 s 范围进行排序的原因(请参阅 NaN 是关联容器的有效键值吗?)。a
b
float
与 ur 和 关系不同,我们不能简单地将其视为正/负无穷大值。Min
Max
std::nullopt
C++ 17 或更早版本
在较旧的 C++ 标准中,您可以使用删除-擦除习惯用语就地消除元素:std::nullopt
std::remove_if
vec.erase(std::remove_if([](const HM& h) {
return !h.has_value();
}, vec.end());
auto minmax = std::minmax_element(vec);
std::cout << minmax.first->H->A << ' ' << minmax.second->H->A << '\n';
实际上,如果您的目标只是找到最小和最大元素,则使用它就足够了。
显然,总是可以选择单独使用 和。std::remove_if
std::min_element
std::max_element
C++20
我们可以用来查看我们的范围,而无需内部任何元素,从而可以正常工作。std::ranges::filter_view
std::nullopt
std::ranges::minmax_element
auto filter = std::ranges::filter_view(vec, [](const HM& h) {
return h.H.has_value();
});
auto minmax = std::ranges::minmax_element(filter, CompareHM);
std::cout << minmax.min->H->A << ' ' << minmax.max->H->A << '\n';
评论
ranges::filter_view
一个大问题是,为了使用,您需要决定 valueless 是否应该大于或小于 a value 的可选。不能两者兼而有之。一个简单的解决方案是通过对第一个进行分区来排除 s 是无值的。std::minmax_element
optional
HM
H
vector
你也可以让它更简单。为:default
operator<=>
optional
- 仅当 lhs 和 rhs 都包含值时,才会比较包含的值(使用相应的运算符 )。否则
T
- 当且仅当 LHS 和 RH 都不包含值时,LHS 才被视为等于 RHS。
- 当且仅当 RHS 包含值而 LHS 不包含值时,才认为 LHS 小于 RHS。
这适用于需要严格周排序的算法,因此:
struct AE {
double A;
double E;
auto operator<=>(const AE& other) const = default;
};
struct HM {
std::optional<AE> H;
std::optional<AE> M;
auto operator<=>(const HM&) const = default;
};
然后对 进行分区,将 s 与无值的最后放在 :vector
HM
H
vector
auto pp = std::stable_partition(vec.begin(), vec.end(), [](auto&& hm) {
return hm.H.has_value();
});
然后可以完成它的工作:minmax_element
auto [min, max] = std::minmax_element(vec.begin(), pp);
如果你不想分区,你也可以使用一个像 Jan 显示的那样,除了你会排除自定义比较器。vector
ranges::filter_view
在 C++17 及更早版本中,您可以为这两个类定义,以便简化工作。仍然不需要复杂的自定义比较器:operator<
struct AE {
double A;
double E;
bool operator<(const AE& other) const {
return std::tie(A, E) < std::tie(other.A, other.E);
}
};
struct HM {
std::optional<AE> H;
std::optional<AE> M;
bool operator<(const HM& other) const {
return std::tie(H, M) < std::tie(other.H, other.M);
}
};
分区和调用的工作方式与上述相同。minmax_element
评论
HMMax