使用std::complex<double>作为std::map的键

6

如何在map中使用复数作为key?以下是一个无法编译的小例子:

#include <complex>
#include <map>
int main() {
  std::complex<double> zero = 0.0;
  std::map<std::complex<double>, int> theMap;
  return (theMap.count(zero));
}

我可以创建地图而不出现错误,但任何方法(例如上面的count调用以及find[]运算符,insert等)都会生成编译时错误。这绝对是我的理解问题,因为我使用clang和g++得到类似的结果。
看起来编译器无法比较两个复数。我创建了所有比较运算符(例如bool operator< (const std::complex & lhs, const std::complex & rhs) {return (std::norm(lhs) < std::norm(rhs));}),这适用于比较复数(只要您不介意3 < -5是true,对于map应该没问题),但编译器没有捕捉到它。
我在unordered_map中也遇到类似的问题(complex<double>没有哈希)。

4
你介意在你的比较方案中将1 == -1 == i == -i(以及它们与其他复数相等)吗?你只能插入其中一个到映射表中。当然,每个相等集合都是一样的。 - NPE
2
欢迎来到StackOverflow。你具体遇到了哪些错误? - Ashalynd
2
考虑改用 (lhs.real() < rhs.real()) || (lhs.real() == rhs.real() && lhs.imag() < rhs.imag()) - Timothy Shields
2
@WhozCraig 复数没有自然排序。同样,std::complex 类型没有内置的比较运算符(除了 ==!=)。 - Timothy Shields
2
@TimothyShields,我切换到了严格的std=c++11,结果出现了operator <不存在的问题。非常感谢你指出这个问题。真的没有想到。非常感谢! - WhozCraig
显示剩余5条评论
3个回答

2
您的方法存在问题,对于有序容器(例如 std::map),键必须适应于严格弱排序。来自wikipedia的定义如下:
对于S中的所有x和y,
对于所有x,不是x < x(反自反性)。
对于所有x、y,如果x < y,则不是y < x(非对称性)。
对于所有x、y和z,如果x < y且y < z,则x < z(传递性)。
对于所有x、y和z,如果x与y不可比较,并且y与z不可比较,则x与z不可比较(不可比较性的传递性)。
很遗憾,对于复数,没有自然的方法来强制实施严格弱序。例如,是1 还是 i <1?如果你说 - ≮ 1和1 ≮ ,那么根据规则严格弱序,-必须成立。这是没有意义的。 需要使用复数作为键,不太可能需要有序容器。尝试查看std :: unordered_map。 从C ++11标准的第23.2.4.2节[associative.reqmts]中: 每个关联容器都是在键和诱导Key元素上的严格弱序比较(25.4)的比较关系Compare参数化的。此外,map和multimap将任意类型T与Key相关联。类型Compare的对象称为容器的比较对象。

@davidhigh,容器的内部算法假定具有传递性。将其视为构建容器对象的前提条件。 - ThomasMcLeod
标准词典排序——虽然在代数中有点无用——满足所述要求。这就是我答案中使用的std::array<T,2>进行比较的方法。 - davidhigh
@davidhigh,没有逻辑或数学上的理由偏爱实数轴而不是复数轴,这是你排序的问题。例如,为什么1 + -10000 _i_应该比-1 + 10000 _i_大?在某些特殊情况下可能有意义的一种可能顺序是将复数转换为极坐标形式,然后按绝对值或参数(但不能同时)排序。 - ThomasMcLeod
1
在“实数乘以实数”的任意排序中,都会偏好某些内容。但它是否适用于实际应用取决于具体情况 [例如,对于实数,我的排序是完美的;对于虚数,可能效率稍低]。但这并不意味着不能使用它。有些排序可能更适合某些关键数据,而其他排序可能更适合其他关键数据,这没有白嫖的事情。但实际上,任何排序都可以工作 - davidhigh

1

我还没有查看实现,但是根据cppreference std::map的说明,它使用std::less<T>作为比较运算符。这可能没有针对std::complex进行特殊化,如果您实现了一个,请将其作为第三个参数传递给模板std::map<std::complex, int, LessOperator>,类似于std:unordered_map,在那里您可以提供哈希函数和相等函数。如果这两个都被实现了,您就可以使用std::complex作为键。


如果std::less<T>没有显式的特化(它确实没有),那么它将执行lhs < rhs(这也未定义为std::complex<T>)。但是,您关于解决方案是第三个模板参数的说法是正确的。 - Bill Lynch
我现在很好奇,您是不是在说如果实现了std::less<T>,即使没有为给定类实现operator<()lhs < rhs也是有效的? - Harald Scheirich
抱歉让您感到困惑。该实现提供了默认专业化。因此,如果没有更好的std::less专业化,例如在这种情况下和std::less<int>中,那么它会默认使用上述专业化。该专业化将尝试执行lhs < rhs,但将失败。基本上,这就是为什么错误消息是std::complex<double>没有实现lhs < rhs而不是std::less<std::complex<double>>不存在的原因。 - Bill Lynch

1
主要答案已在上面的评论中指出。问题在于复数是无序的,因此std::complex<T>没有预定义的小于运算符。尽管如此,您可以为自己定义一个,并且通过使用已定义的std::array<T,2>的小于运算符来轻松完成这一操作。请注意,保留html标签。
#include <iostream>
#include<complex>
#include<map>
#include<unordered_map>
#include<array>

template<typename T> struct less {};

    template<typename T>
    struct less<std::complex<T> >
    {
        bool operator()(std::complex<T> const& a, std::complex<T> const& b)
        {
            return std::array<T,2>{a.real(),a.imag()} < std::array<T,2>{b.real(),b.imag()};
        }
    };

int main()
{
    std::map<std::complex<double>, int, less<std::complex<double> > > m;

    m[std::complex<double>(1.0,0.0)]=1;
    m[std::complex<double>(0.0,1.0)]=2;
    m[{0.5,0.5}]=3;

    return 0;
}

查看实时示例 here

1
不允许将自己的内容添加到命名空间std中。 - M.M
@Matt McNabb:你说得对,因为std::complex不是用户定义的类型。我通过删除namespace std来编辑我的答案。 - davidhigh
关于向namespace std添加内容,请参见此答案,其中指出只有针对用户定义类型(而std::complex不是)才可能进行特化。然而,在这篇博客中,它指出尽管这是未定义的行为,但现代编译器允许这样做(除非该特化已经存在)...这就是为什么它在这里起作用的原因。 - davidhigh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接