2015-10-30 3 views
4

Допустим, у меня есть вектор строк, и я хочу, чтобы найти все строки, которые начинаются с 'a', так что я могу это сделать:станд :: equal_range с лямбда

struct cmp { 
    bool operator()(const std::string &s, char c) const { return s.front() < c; } 
    bool operator()(char c, const std::string &s) const { return s.front() < c; } 
}; 
std::vector<std::string> strings; 
... 
std::sort(strings.begin(), strings.end()); 
auto range = std::equal_range(strings.begin(), strings.end(), 'a', cmp{}); 
... 

Этот метод подвержен ошибкам, так как легко ошибиться (например, я думаю, что это должно быть c < s.front() во втором методе) и имеет дублирование кода.

Так можно ли реализовать функцию сравнения с общей лямбдой вместо структуры с помощью 2 методов?

Более общий вопрос, почему значение для сравнения должно быть принят в качестве аргумента std::lower_bound, std::upper_bound и std::equal_range, когда она легко может быть захвачена лямбда или передается в структуре сравнения, а затем этот вопрос не будет там вообще?

Как это могло бы работать, если std::equal_range не требовало бы стоимости?

struct cmp { 
    cmp(char lc) : c(lc) {} 
    bool operator()(const std::string &s) const { return s.front() < c; } 
    char c; 
}; 
std::vector<std::string> strings; 
... 
std::sort(strings.begin(), strings.end()); 
auto range = std::equal_range(strings.begin(), strings.end(), cmp{'a'}); 
+0

Если вы готовы передать '' a '' вместо '' a'', тогда будет работать лямбда с двумя строками и сравнением их первых символов. В противном случае названный класс с двумя перегрузками выглядит как самое чистое решение для меня. Обратите внимание, что вторая перегрузка, как написано в настоящее время, делает ее сравнение неправильным образом. –

+0

@IgorTandetnik это очевидно, но это может быть не строка, а объект, который отсортирован по его части, поэтому я не хочу создавать целые объекты только для использования в качестве ключа. – Slava

+1

Вы можете использовать 'std :: partition_point'. –

ответ

4

lower_bound является

std::partition_point(strings.begin(), strings.end(), 
        [](const auto& s) { return s.front() < 'a'; }); 

upper_bound является

std::partition_point(strings.begin(), strings.end(), 
        [](const auto& s) { return s.front() <= 'a'; }); 

Да, это означает, что вы должны написать два вызова, чтобы получить эквивалент equal_range. Вы можете обернуть это в свободный шаблон:

template<class Iter, class T, class Proj> 
std::pair<Iter, Iter> my_equal_range(Iter first, Iter last, const T& value, Proj proj) { 
    auto b = std::partition_point(first, last, [&](const auto& s) { return proj(s) < value; }); 
    auto e = std::partition_point(b, last, [&](const auto& s) { return !(value < proj(s)); }); 
    return {b, e}; 
} 

И называют его

my_equal_range(strings.begin(), strings.end(), 'a', 
       [](const auto& s) { return s.front(); }); 

диапазонами TS рабочий проект добавляет проекции к алгоритмам, так что вы (в конечном счете) быть в состоянии сделать это:

std::experimental::ranges::equal_range(strings, 'a', {}, 
             [](const auto& s) { return s.front(); }); 
+0

Было бы более эффективным для второго 'std: : partition_point', чтобы использовать вывод предыдущего вызова вместо 'first'? – Slava

+0

@Slava Конечно, это было бы более эффективно. –

+0

вы можете на самом деле повторно использовать' first' и 'last' :)' first = ...; last = ...; return {first, last}; ' – Slava

4

Чтобы ответить на ваш первый вопрос, может ли компаратор быть реализован с использованием общей лямбда, да, это возможно. Вам также понадобится пара вспомогательных функций, чтобы вернуть желаемый результат с аргументом char или string.

auto get_char(char c)    { return c; } 
auto get_char(std::string const& s) { return s.front(); } 
auto cmp = [](auto const& l, auto const& r) { return get_char(l) < get_char(r); }; 

Live demo


Одна из причин, вы не можете просто быть компаратор захватить значение потому, что две перегрузки equal_range может затем быть неоднозначным, вам нужно немного другое имя или какой-то другой путь (например, аргумент тега), чтобы устранить двузначность.

+0

Хорошее решение, но не идеальное :(У него такая же проблема, которая мешала мне использовать алгоритмы перед лямбдой - вам нужно писать функторы вне области кода, где он используется. – Slava