Использование std::equal_range для поиска диапазона префиксов, встречающихся в векторе строк

Я пытаюсь придумать лямбду, которая позволит std::equal_range возвращать диапазон, в котором искомая строка существует в качестве префикса. Поскольку это, вероятно, неправильно сформулировано, пример:

Учитывая вектор строк:

  • C:\users\andy\documents\screenshot.jpg
  • C:\users\bob\desktop\file.txt
  • C:\users\bob\рабочий стол\picture.png
  • C:\users\bob\рабочий стол\video.mp4
  • C:\users\джон\рабочий стол\note.txt

Я ожидаю, что итераторы вернутся

  • C:\users\bob\desktop\file.txt и
  • C:\users\bob\desktop\video.mp4.

Как мне написать сравнительную лямбду для std::equal_range, которая выполняет это, или std::equal_range не подходит для этой работы?


person Andrew LeFevre    schedule 28.12.2017    source источник
comment
Подстрока или префикс? подстрока может встречаться в любом месте строки, и результаты не обязательно должны быть непрерывными.   -  person Retired Ninja    schedule 28.12.2017
comment
Думаю, я почти всегда буду искать префикс, хороший улов.   -  person Andrew LeFevre    schedule 28.12.2017
comment
Четырехаргументная перегрузка std::equal_range с вызываемым объектом сравнения кажется достаточной для задачи. Просто закодируйте объект сравнения, который реализует сравнение, соответствующее префиксу. Вам придется тщательно кодировать объект сравнения с отдельными реализациями comp(element, value) и comp(value, element). , чтобы вернуть правильный результат сравнения.   -  person Sam Varshavchik    schedule 28.12.2017
comment
Что ж, начнем с основ. Учитывая функцию, которая принимает полную строку и префикс в качестве параметра, возвращает значение bool, указывающее, сортируется ли полная строка перед префиксом. Это смехотворно тривиально, и после того, как вы написали, вы уже сделали 50%. Остальные 50% — это функция, которая принимает префикс и полную строку и возвращает true, если префикс сортируется перед полной строкой. Задача решена.   -  person Sam Varshavchik    schedule 28.12.2017
comment
Кажется, это работает, но я не уверен, что это правильно: ideone.com/NiV02v Возможно, вы могли бы избежать substr использовал std::string.compare, но мне было понятнее, когда я смотрел на него. пожал плечами   -  person Retired Ninja    schedule 28.12.2017
comment
Я бы выбрал немного более сложный подход, чем у ниндзя на пенсии. Я бы принудил строку префикса к отдельному вспомогательному типу, который не является std::string. Затем я могу различать два типа функций сравнения и реализовывать два operator() в пользовательском классе компаратора, сравнивая префикс со строкой и префикс со строкой.   -  person Sam Varshavchik    schedule 28.12.2017
comment
Да, это был просто способ заставить его работать вообще. Две разные функции упрощают понимание того, как они будут использоваться с lower_bound/upper_bound.   -  person Retired Ninja    schedule 28.12.2017
comment
@RetiredNinja, кажется, работает отлично! Я просто не понимаю, почему сравнение по ‹ в операторах if работает, и почему сравнение по ‹= не нужно?   -  person Andrew LeFevre    schedule 28.12.2017
comment
а как насчет C:\users\bob\desktop\picture.png почему его нельзя вернуть?   -  person Killzone Kid    schedule 28.12.2017
comment
Использование <= или >= нарушает строгое требование слабого порядка для сравнения. Этот ответ объясняет это лучше, чем я мог. :) stackoverflow.com/a/981299/920069   -  person Retired Ninja    schedule 28.12.2017
comment
@KillzoneKid std::equal_range возвращает диапазон, первый соответствующий элемент и последний соответствующий элемент. Таким образом, C:\users\bob\desktop\picture.png находится в диапазоне между C:\users\bob\desktop\file.txt и C:\users\bob\desktop\video.mp4.   -  person Andrew LeFevre    schedule 28.12.2017
comment
@AndrewLeFevre Попался!   -  person Killzone Kid    schedule 28.12.2017
comment
@SamVarshavchik В чем преимущество вашего более сложного подхода? Это вообще улучшит производительность? Просто интересно, объективно ли ваш подход в чем-то лучше.   -  person Andrew LeFevre    schedule 28.12.2017
comment
Это избавляет компаратор от необходимости явно захватывать префикс вне диапазона, чтобы выяснить, какое значение параметра является префиксом. Вероятно, немного быстрее, нет необходимости делать это сравнение при каждом вызове; но я сомневаюсь, что это будет иметь какое-либо значение для современных процессоров с несколькими ГГц.   -  person Sam Varshavchik    schedule 29.12.2017
comment
@SamVarshavchik о, хорошо, спасибо!   -  person Andrew LeFevre    schedule 29.12.2017


Ответы (1)


Я думаю, вам просто нужно заставить компаратор сравнивать только длину префикса с такими элементами:

std::vector<std::string> v
{
    "C:/users/andy/documents/screenshot.jpg",
    "C:/users/bob/desktop/file.txt",
    "C:/users/bob/desktop/picture.png",
    "C:/users/bob/desktop/video.mp4",
    "C:/users/john/desktop/note.txt",
};

std::sort(std::begin(v), std::end(v));

std::string const prefix = "C:/users/bob/desktop/";

auto lb = std::lower_bound(std::begin(v), std::end(v), prefix);

// For the upper bound we want to view the vector's data as if
// every element was truncated to the size of the prefix.
// Then perform a normal match.
auto ub = std::upper_bound(lb, std::end(v), prefix,
[&](std::string const& s1, std::string const& s2)
{
    // compare UP TO the length of the prefix and no farther
    if(auto cmp = std::strncmp(s1.data(), s2.data(), prefix.size()))
        return cmp < 0;

    // The strings are equal to the length of the prefix so
    // behave as if they are equal. That means s1 < s2 == false
    return false;
});

// make the answer look like we used std::equal_range
// (if that's what's needed)
auto range = std::make_pair(lb, ub);

for(auto itr = range.first; itr != range.second; ++itr)
    std::cout << *itr << '\n';

Вывод:

C:/users/bob/desktop/file.txt
C:/users/bob/desktop/picture.png
C:/users/bob/desktop/video.mp4

Чтобы объяснить, почему это работает, представьте, что вы берете вектор и сортируете его. Затем представьте, что вы посещаете каждый элемент и усекаете его до длины префикса. У вас останется отсортированный вектор без элементов длиннее префикса. В этот момент простой std::equal_range сделает то, что вам нужно. Таким образом, все, что нам нужно сделать, это создать компаратор, который ведет себя как если бы элементы контейнера были усечены до длины префикса, и использовать этот компаратор в нашем std::equal_range поиске ( или двойной std::lower_bound/upper_bound поиск).

person Galik    schedule 28.12.2017
comment
Из того, что я понимаю из комментариев Сэма Варшавчика выше, вы должны сравнивать, используя как comp (элемент, значение), так и comp (значение, элемент), верно? Кроме того, будет ли здесь использование string::compare быстрее? - person Andrew LeFevre; 29.12.2017
comment
@AndrewLeFevre Да, ты прав. Это сложнее, чем я думал. Я уделю этому больше внимания. - person Galik; 29.12.2017
comment
@AndrewLeFevre Я считаю, что теперь это исправлено. - person Galik; 29.12.2017