Использование 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. NaN нарушит ваш оператор сравнения в отношении строгих требований к слабому порядку. Если это соответствует вашим требованиям и у вас есть некоторая свобода действий, рассмотрите возможность использования неупорядоченной карты с пользовательским оператором сравнения, который вместо этого проверяет наличие NaN.   -  person bluescarni    schedule 08.10.2014


Ответы (3)


Я не смотрел на реализацию, но per 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, ключ должен поддаваться строгому слабому упорядочению. Из википедии:

Строгий слабый порядок обладает следующими свойствами. Для всех 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:

Каждый ассоциативный контейнер параметризуется по ключу и упорядочивающему отношению сравнения, которое вызывает строгое слабое упорядочение (25.4) элементов ключа. Кроме того, карты и мультикарты связывают произвольный тип 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