Алгоритм поиска строк

Тема в разделе "WASM.HEAP", создана пользователем _DEN_, 23 фев 2012.

  1. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Привет. Помогите придумать алгоритм, желательно в рамках STL/Boost функций и алгоритмов. Нужно вот что:

    1. Имеется массив пар "ключ(строка) -> объект". Можно считать, что этот массив упорядочен по "ключ" любым удобным образом. Ключи могут повторяться.
    2. На вход приходит строка, назовем ее "значение".
    3. Нужно найти все пары "ключ -> объект" в имеющемся массиве, в которых "значение" начинается с "ключ".
    4. Среди найденных пар нужно найти пары, у которых длина "ключ" максимальна. Таких пар может быть несколько.

    Решить надо за O(ln(n)). Догадываюсь что надо сделать что-то типа std::equal_range с собственным предикатом, и заставить его искать методом бинарного поиска, но вот что-то не связать алгоритмы вместе чтобы получилось то что надо.

    Edit: поправил термины, было не совсем верно)
     
  2. l_inc

    l_inc New Member

    Публикаций:
    0
    Регистрация:
    29 сен 2005
    Сообщения:
    2.566
    _DEN_
    Я эту же задачу решал на haskell. :) Подходящая структура называется префиксное дерево.

    Edit: согласно поправке добавляется возможность поиска по хэш-таблице, перебирая все префиксы "значения". Но префиксное дерево всё ещё в силе (и, очевидно, эффективнее). Сложность в обоих случаях константная (не зависит от числа пар в выбранном ассоциативном контейнере).
     
  3. kaspersky

    kaspersky New Member

    Публикаций:
    0
    Регистрация:
    18 май 2004
    Сообщения:
    3.006
    _DEN_
    поиск по multi-pattern search должен помочь. n -- это у вас что? от кол-ва ключей время поиска не зависит (если есть память), а зависит оно только от длины строки, которая приходит на вход. и зависит оно как O(n). быстрее -- нельзя. но если длинной строки можно пренебречь, то получаем O(1).

    как всегда для ответа на вопрос не хватает данных. "значение" какой длинны? какое мы имеем распределение по str[m..k]? если можно выделить часть строки, которая для большинства строк разная и коллизий будет немного, то считаем хэш, делаем первичную выборку и дальше уже доруливаем сравнением элементов. при этом мы в идеале имеем O(1) и минимальный оверхид на память.
     
  4. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    l_inc kaspersky

    Все же немного не ясно. Приведу пример. Ключи - это урлы:

    / -> 1
    /about/ -> 2
    /contact/ -> 3
    /forum/ -> 4
    /forum/ -> 5
    /forum/thread/ -> 6
    /forum/thread/ -> 7
    /logout/ -> 8

    На вход приходит строка /forum/message/. Наиболее подходящие пары - это 4 и 5. Они удовлетворяют обоим условиям: 1) входящая строка начинается с ключа. Ключ имеет максимальную длину (среди пар, удовлетворяющих п.1).

    n = 8 = количество пар. Можно на пальцах, как получить 4 и 5 за o(ln(n))?
     
  5. l_inc

    l_inc New Member

    Публикаций:
    0
    Регистрация:
    29 сен 2005
    Сообщения:
    2.566
    _DEN_
    Префиксное дерево.
    Идём по дереву посимвольно (переход к следующему поддереву — минимальная константная сложность). Первый символ "\" не отсеивает ни одного элемента (но он сразу отображается на 1, запомним это). Второй символ "f" отсеивает половину элементов. Третий символ "o" и т.д. Когда переходим ко второму слешу, натыкаемся на множество с элементами 4 и 5 (запомним это). Следующее поддерево с символом "m" отсутствует (указатель NULL или просто отсутствие элемента в зависимости от реализации дерева). Последнее найденное отображение отображает самый длинный префикс. Сложность константная, никаких логарифмов. Линейная относительно длины входной строки, но лучше линейной сделать невозможно.

    Хеш-таблица
    Опрашиваем таблицу на префикс "/" (сложность опроса каждого символа константная). Находим отображение на 1 (запоминаем). Опрашиваем таблицу на префикс "/f" (ничего не находим). Опрашиваем таблицу на префикс "/fo" (ничего не находим) и т.д. Когда подходим к префиксу "/forum/", натыкаемся на отображение на множество с 4 и 5 (запоминаем). Далее проходим оставшиеся префиксы до конца входной строки и ничего не находим. Последнее найденное отображение отображает самый длинный префикс. Как видно, опять сложность константная. Но теперь с учётом хеширования квадратичная относительно длины строки. Кроме того, в отличие от варианта с префиксным деревом строку нужно проходить до конца, чтобы убедиться в отсутствии отображений более длинных префиксов.

    Поэтому, как я и сказал в предыдущем посте, очевидно, что вариант с префиксным деревом эффективнее.
     
  6. letopisec

    letopisec New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2004
    Сообщения:
    228
    _DEN_ совместимость пунктов 2 и 3 после фразы: " в которых 'значение' начинается с 'ключ'.", просто выносит мозг. Только после примера стало болемене чета ясно.

    Предлагаю использовать предикат, и менять критерий поиска в зависимости от того ищешь для инсерта или для поиска ключа с которого начинается твое значение

    че та типа как то так:

    Код (Text):
    1. class sort_cr
    2. {
    3. public:
    4.     bool operator () (const std::string lhs, const std::string rhs)
    5.     {
    6.         if (rhs[0] == '@')
    7.         {
    8.             if (rhs.find(lhs) == 1)
    9.                 return false;
    10.             return lhs > (rhs.c_str() + 1);
    11.         }
    12.         else if (lhs[0] == '@')
    13.         {
    14.             if (lhs.find(rhs) == 1)
    15.                 return false;
    16.             return rhs < (lhs.c_str() + 1);
    17.         }
    18.         return rhs < lhs;
    19.     }
    20. };
    21.  
    22.  
    23. int main()
    24. {
    25.     sort_cr pr;
    26.     std::multimap<std::string, int, sort_cr> m(pr);
    27.     m.insert(std::make_pair("/",1));
    28.     m.insert(std::make_pair("/about/",2));
    29.     m.insert(std::make_pair("/contact/",3));
    30.     m.insert(std::make_pair("/forum/",4));
    31.     m.insert(std::make_pair("/forum/mess",5));
    32.     m.insert(std::make_pair("/forum/thread/",6));
    33.     m.insert(std::make_pair("/forum/thread/",7));
    34.     m.insert(std::make_pair("/logout/",8));
    35.  
    36.     BOOST_TYPEOF(m)::iterator it = m.find("@/forum/message"); // приаттаченый символ собаки для распознания что в чем ищем
    37.     return 0;
    38. }
     
  7. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    l_inc
    Ага, спасибо, теперь понял.

    letopisec
    Ай, шайтан! Клевая идея с приаттаченным символом. Я тоже думал что решение было бы простым, если бы можно было юзать разные предикаты на вставку и на поиск, но через аттач символа как-то не догадался :) Несколько вопросов:
    1) Зачем предикат объявляется объектом на стеке? Разве просто так нельзя?
    2) Я правильно понимаю что поиск найдет гарантированно первый итератор от нужного range?
    3) Наверно вместо string::find лучше юзать boost::starts_with? Ведь первый будет искать до упора, а второй - до первой разницы?
    3) UPD: Наверно лучше даже свой starts_with написать, чтобы избежать создания временных строк и учесть символ '@'?
     
  8. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Решил '@' не включать в функцию сравнения - имхо ничего хорошего это не дает. В общем оформил все по уставу, вот что получилось:

    Код (Text):
    1. template <typename T>
    2. class less : std::binary_function<std::basic_string<T>, std::basic_string<T>, bool>
    3. {
    4. public:
    5.  
    6.     typedef T value_type;
    7.     typedef std::basic_string<value_type> string_type;
    8.  
    9.     bool operator () (string_type const& lhs, string_type const& rhs) const
    10.     {
    11.         if(rhs[0] == static_cast<value_type>('@')) // Каст законный, ASCII нам гарантирует это :)
    12.         {
    13.             if(starts_with(rhs.c_str() + 1, lhs.c_str()))
    14.             {
    15.                 return false;
    16.             }
    17.             else
    18.             {
    19.                 return lhs > (rhs.c_str() + 1);
    20.             }
    21.         }
    22.         else if(lhs[0] == static_cast<value_type>('@'))
    23.         {
    24.             if(starts_with(lhs.c_str() + 1, rhs.c_str()))
    25.             {
    26.                 return false;
    27.             }
    28.             else
    29.             {
    30.                 return rhs < (lhs.c_str() + 1);
    31.             }
    32.         }
    33.         else
    34.         {
    35.             return rhs < lhs;
    36.         }
    37.     }
    38.  
    39. private:
    40.  
    41.     // Давно не писал подобные штуки, надеюсь что получилось не слишком страшно :)
    42.     // UPD: работа над ошибками
    43.     static bool starts_with(value_type const* str, value_type const* target)
    44.     {
    45.         for(; *target && *str == *target; ++str, ++target);
    46.         return !*target;
    47.     }
    48. };
     
  9. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    letopisec

    Решил что вместо '@' можно сохранять указатель за строку-find внутри предиката, и в operator () просто сравнивать значения указателей. Тогда логика получается чище.
     
  10. letopisec

    letopisec New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2004
    Сообщения:
    228
    _DEN_

    А в чем сокральный смысл наледования от binary_function и статик каста к value_type?
     
  11. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    letopisec

    Ну это как в анекдоте про клетку и пять обезьян :) Так делают в STL и Boost. Х.з., возможно - это прадедушка концептов )

    Статик каст - на случай если где-нибудь какой-нибудь компилятор на максимальном уровне варнингирования решит написать ахтунг о том, что сравниваются разнотипные элементы (если класс будет для std::wstring).