Подтвердить что ты не робот

Мистическое ограничение на std:: binary_search

Описание проблемы:
Рассмотрим некоторую структуру, имеющую член std::string name. Для ясности пусть пусть a struct Human, представляя информацию о людях. Помимо name он может также содержать много других элементов данных.
Пусть есть контейнер std::vector<Human> vec, где объекты уже отсортированы по name. Также для ясности предположим, что все имена уникальны.
Проблема заключается в следующем: наличие некоторой строки nameToFind выяснить, существует ли элемент в массиве с таким именем.

Решение и мой прогресс:
Очевидное и естественное решение, похоже, выполняет двоичный поиск с использованием функции std::binary_search. Но есть проблема: тип искомого элемента (std::string) отличается от типа элементов в контейнере (Human), а std:: binary_search требует правила для сравнения этих элементов. Я попытался решить это тремя способами, описанными ниже. Первые два представлены только для иллюстрации эволюции моего решения и проблем, с которыми я столкнулся. Мой главный вопрос относится к третьему.

Попытка 1: конвертировать std::string в Human.

Напишите функцию сравнения:

bool compareHumansByNames( const Human& lhs, const Human& rhs )
{
   return lhs.name < rhs.name;
}

Затем добавьте конструктор, который строит объект Human из std::string:

struct Human
{
   Human( const std::string& s );
   //... other methods

   std::string name;
   //... other members
};

и используйте binary_search в следующей форме:

std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames );

Кажется, работает, но появляется две большие проблемы:
Во-первых, как инициализировать другие члены данных, но Human::name, особенно в случае, когда у них нет конструктора по умолчанию? установка магических значений может привести к созданию объекта, который является семантически незаконным.
Во-вторых, мы должны объявить этот конструктор как не explicit, чтобы разрешить неявные преобразования во время алгоритма. Плохие последствия этого хорошо известны.
Кроме того, такой временный объект Human будет построен на каждой итерации, что может оказаться довольно дорогостоящим.

Попытка 2: конвертировать Human в std::string.

Мы можем попытаться добавить operator string () в класс Human, который возвращает его name, а затем использовать сравнение для двух std::string s. Однако этот подход также неудобен по следующим причинам:

Во-первых, код не будет компилироваться сразу из-за обсуждаемой проблемы здесь. Нам придется немного поработать, чтобы компилятор использовал соответствующий operator <.
Во-вторых, что означает "преобразовать человека в строку"? Существование такого преобразования может привести к семантически неправильному использованию класса Human, что нежелательно.

Попытка 3: сравнение без конверсий.

Лучшим решением, которое я получил до сих пор, является создание

struct Comparator
{
   bool operator() ( const Human& lhs, const std::string& rhs )
   {
      return lhs.name < rhs;
   }
   bool operator() ( const std::string& lhs, const Human& rhs )
   {
      return lhs < rhs.name;
   }
};

и использовать двоичный поиск как

binary_search( vec.begin(), vec.end(), nameToFind, Comparator() );

Это компилируется и выполняется правильно, все выглядит нормально, но здесь начинается интересная часть:

Посмотрите http://www.sgi.com/tech/stl/binary_search.html. Здесь сказано, что тип значения ForwardIterator того же типа, что и T. ". Довольно запутанное ограничение, и мое последнее решение нарушает его. Посмотрите, что говорит об этом стандарт С++:


25.3.3.4 binary_search

template<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value);

template<class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp);

Требуется: Тип T является LessThanComparable (20.1.2).


В явном виде ничего не сказано о типе ForwardIterator. Но в определении LessThanComparable, приведенном в 20.1.2, говорится о сравнении двух элементов того же типа. И вот что я не понимаю. Действительно ли это означает, что тип исследуемого объекта и тип объектов контейнера должны совпадать, и мое решение нарушает это ограничение? Или это не относится к случаю, когда используется компаратор comp и относится только к случаю, когда по умолчанию используется operator < для сравнения? В первом случае я смущен тем, как использовать std::binary_search для решения этой проблемы, не сталкиваясь с проблемами, упомянутыми выше.

Заранее благодарим за помощь и находим время, чтобы прочитать мой вопрос.

Примечание. Я понимаю, что запись двоичного поиска вручную не требует времени и решит проблему мгновенно, но чтобы избежать повторного создания колеса, я хочу использовать std:: binary_search. Также мне очень интересно узнать о существовании такого ограничения в соответствии со стандартом.

4b9b3361

Ответ 1

Если ваша цель - найти, есть ли Human с заданным именем, то необходимо убедиться в следующем:

const std::string& get_name(const Human& h)
{
    return h.name;
}

...

bool result = std::binary_search(
    boost::make_transform_iterator(v.begin(), &get_name),
    boost::make_transform_iterator(v.end(), &get_name),
    name_to_check_against);

Ответ 2

[Завершить переписывание; игнорировать комментарии]

Формулировка была изменена с С++ 03 на С++ 0x. В последнем случае для T больше не требуется быть меньше, чем сопоставимо, по-видимому, для облегчения этого ненужного ограничения.

Новый стандарт требует только, чтобы comp(e, value) влечет !comp(value, e). До тех пор, пока ваш компаратор реализует оба направления, вы должны иметь возможность юридически искать string как значение с помощью функтора компаратора, который реализует как асимметричные сравнения (т.е. Ваш "попытка 3" ).

Ответ 3

Я думаю, что здесь говорится в стандарте, что выражение fucntor(a, b) должно быть допустимым строгим слабым порядком, независимо от того, решил ли алгоритм сделать что-то вроде functor(*begin, *(begin + 1)). Поэтому, я думаю, ваш компаратор должен будет обеспечить перегрузку operator()(Human, Human), чтобы соответствовать.

Тем не менее, я думаю, что это одна из тех вещей, которые явно не разрешены стандартом, но для которых существует мало или вообще не существует реализаций, которые используют широту, предлагаемую стандартным.

Ответ 4

Я не вижу, чтобы в стандарте требовалось, чтобы типы значений, переданных функции сравнения (или оператору <) на binary_search, должны быть одинаковыми. Итак, формально я считаю, что отлично использовать компаратор, который работает с двумя значениями разных типов.