Използване на std::complex‹double› като std::map ключ

Как мога да използвам комплексно число като ключ в карта? Ето малък пример, който няма да се компилира:

#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 да е вярно, което трябва да е добре за map), но компилаторът не го приема.

Имам подобни проблеми с unordered_map (няма хеш за complex<double>)


person Brett Diamond    schedule 07.10.2014    source източник
comment
Нямате ли нищо против, че във вашата схема за сравнение 1 == -1 == i == -i (и всички те са равни на много други комплексни числа)? Ще можете да вмъкнете само един от тях в картата. Същото, разбира се, важи и за всеки друг набор за равенство.   -  person NPE    schedule 08.10.2014
comment
Добре дошли в StackOverflow. Какви грешки по-конкретно получавате?   -  person Ashalynd    schedule 08.10.2014
comment
Създадох всички оператори за сравнение... какво ще кажете да публикувате целия код?   -  person Cornstalks    schedule 08.10.2014
comment
Помислете вместо това да използвате (lhs.real() < rhs.real()) || (lhs.real() == rhs.real() && lhs.imag() < rhs.imag()).   -  person Timothy Shields    schedule 08.10.2014
comment
@WhozCraig Няма естествено подреждане на комплексните числа. По същия начин няма вградени оператори за сравнение (освен == и !=) за типа std::complex.   -  person Timothy Shields    schedule 08.10.2014
comment
@TimothyShields добре е, че std::map не използва нито един от тези оператори. Може би моят libstdc++ дойде със специален сос, но изглежда ги поръчва чрез operator <, както бих очаквал.   -  person WhozCraig    schedule 08.10.2014
comment
@WhozCraig Стандартът не дефинира оператор < за std::complex. libstdc++ може да се предлага с такъв, но може да е дефиниран по различен начин или просто да не е наличен с други доставчици.   -  person Timothy Shields    schedule 08.10.2014
comment
@TimothyShields Превключих на строг std=c++11 и kerboom, без operator <. Благодарим ви много, че посочихте това. Не видях това да идва. Много благодаря!   -  person WhozCraig    schedule 08.10.2014
comment
Внимавайте да използвате каквато и да е форма на double като ключ, тъй като разликите в закръгляването може да ви попречат да извлечете елемента по-късно.   -  person Mark Ransom    schedule 08.10.2014
comment
Като допълнение към казаното от Марк, пазете се от факта, че може да имате NaN във вашия комплекс. NaNs ще нарушат вашия оператор за сравнение по отношение на строгите изисквания за слабо подреждане. Ако отговаря на вашите изисквания и ако имате известна свобода на действие, обмислете използването на неподредена карта с персонализиран оператор за сравнение, който вместо това проверява за NaN.   -  person bluescarni    schedule 08.10.2014


Отговори (3)


Не съм разглеждал изпълнението, но според cppreference std::map използва std::less<T> като оператор за сравнение , който може да не е специализиран за std::complex, ако внедрите едно предаване като трети параметър във вашия шаблон std::map<std::complex, int, LessOperator> подобно на std:unordered_map, където можете да предоставите хеш функтор и функтор за равенство. Ако и двете са внедрени, можете да използвате std::complex като ключ.

person Harald Scheirich    schedule 07.10.2014
comment
Ако std::less<T> няма изрична специализация (което няма), тогава ще направи lhs < rhs (което също не е дефинирано за std::complex<T>). Но сте прави за това, че решението е третият аргумент на шаблона. - person Bill Lynch; 08.10.2014
comment
Сега съм любопитен, казвате ли, че ако std::less<T> е имплементирано, тогава lhs < rhs е валидно дори ако не е имплементирано operator<() за дадения клас? - person Harald Scheirich; 08.10.2014
comment
Извинете за объркването. Внедряването осигурява специализация по подразбиране. Така че, ако няма по-добра специализация на std::less, както в този случай и с std::less<int>, тогава по подразбиране се използва споменатата специализация. Тази специализация ще се опита да направи lhs < rhs, което ще се провали. По принцип това е причината съобщението за грешка да е, че std::complex<double> не прилага lhs < rhs, а не std::less<std::complex<double>> не съществува. - person Bill Lynch; 08.10.2014

Основните отговори са посочени в коментарите по-горе. Проблемът е, че комплексните числа не са подредени и затова няма предварително дефиниран по-малък оператор за std::complex<T>. Въпреки това можете да дефинирате такъв за себе си и това става лесно, като използвате вече дефинирания по-малък оператор за std::array<T,2>:

#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;
}

Вижте пример на живо тук.

person davidhigh    schedule 07.10.2014
comment
Не ви е позволено да добавяте свои собствени неща към пространството от имена std - person M.M; 08.10.2014
comment
@Matt McNabb: прав си, тъй като std::complex не е дефиниран от потребителя тип. Редактирах отговора си, като премахнах namespace std. - person davidhigh; 08.10.2014
comment
По отношение на добавянето на неща към namespace std вижте този отговор, който гласи, че специализация е възможна само за дефинирани от потребителя типове (което std::complex не е). В този блог обаче се посочва, че въпреки че е недефинирано поведение, съвременните компилатори позволяват да го правят (освен ако тази специализация вече не съществува) ... поради което работи тук. - person davidhigh; 08.10.2014

Проблемът с вашия подход е, че за подреден контейнер, като std::map, ключът трябва да може да се поддава на стриктно слабо подреждане. От wikipedia:

Строгият слаб ред има следните свойства. За всички x и y в S,

За всички 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 или i ‹ 1? Ако кажете, че -i ≮ 1 и 1 ≮ i, тогава според правилата за строго слабо подреждане трябва да е вярно, че -iаз. Това означава смисъл.

Трябва да използвате комплексни числа като ключ, малко вероятно е да имате нужда от поръчан контейнер. Опитайте да разгледате std::unordered_map.

От раздел 23.2.4.2 [associative.reqmts] на стандарт C++11:

Всеки асоциативен контейнер е параметризиран на Key и релация за подреждане Compare, която индуцира строго слабо подреждане (25.4) на елементите на Key. Освен това map и multimap свързват произволен тип T с ключа. Обектът от тип Compare се нарича обект за сравнение на контейнер.

person ThomasMcLeod    schedule 07.10.2014
comment
@davidhigh, вътрешните алгоритми на контейнера предполагат транзитивност. Мислете за това като за предварително условие за конструиране на обекта контейнер. - person ThomasMcLeod; 08.10.2014
comment
стандартното лексикографско подреждане - макар и малко безполезно в алгебрата - изпълнява посочените изисквания. Това е, което се прави чрез сравнение на std::array<T,2>, както се използва в моя отговор. - person davidhigh; 08.10.2014
comment
@davidhigh, няма логическа или математическа причина да предпочитате реалната линия пред сложната линия, което прави вашето подреждане. Например, защо 1 + -10000 i трябва да е по-голямо от -1 + 10000 i? Възможен ред, който би могъл да има смисъл в определени специални случаи, е преобразуването на комплексното число в полярна форма и след това подреждане или по абсолютната стойност или по аргумента (но не и по двете). - person ThomasMcLeod; 08.10.2014
comment
всяко подреждане в IR x IR благоприятства нещо и дали е подходящо зависи от действителното приложение [напр. за реални числа моята поръчка е перфектна, за имагинерни вероятно е донякъде неефективна]. Но това не е аргумент срещу използването му. Някои може да са по-добри за някои ключови данни, други за други ключови данни, няма безплатен обяд. Но всъщност всяка поръчка ще работи. - person davidhigh; 08.10.2014