Реализовать sortedMap с сопоставлением подпоследовательностей

Я хочу реализовать sortedMap с ключами и значениями, чтобы ключи можно было искать, предоставив некоторую подпоследовательность. Например, карта содержит 3 записи:

abcd -> obj1
def -> obj2
abccd -> obj3

Для запроса ac результатом должна быть подкарта, содержащая 1-ю и 3-ю записи, но для запроса acc должна быть возвращена только 3-я запись.

Какую структуру данных я должен использовать внутри, чтобы эффективно возвращать такую ​​подкарту? Например, Treemap, который хранит ключи в дереве (trie), чтобы эффективно возвращать подкарту на основе префикса?


person user2436032    schedule 20.04.2015    source источник
comment
для запроса bc должна ли ваша карта возвращать первую и третью записи?   -  person Alex Salauyou    schedule 20.04.2015
comment
Ваша терминология неверна. ac не является подпоследовательностью abcd или abccd.   -  person Stephen C    schedule 20.04.2015
comment
Для запроса ca он должен возвращать 1-ю и 3-ю записи? Или не?   -  person Stephen C    schedule 20.04.2015
comment
@sasha ... да, вы правы, bc должен вернуть 1-ю и 3-ю запись   -  person user2436032    schedule 20.04.2015
comment
Самое простое решение, которое приходит на ум, - это использовать какую-то индексацию с помощью 1-, 2- и, возможно, 3-буквенных подпоследовательностей/перестановок. При добавлении записи вы генерируете все подпоследовательности/перестановки, которые можно получить из данного ключа, а затем добавляете эту запись во все такие сегменты индекса. При запросе вы берете первую подпоследовательность, берете список записей из соответствующей корзины и фильтруете.   -  person Alex Salauyou    schedule 20.04.2015
comment
@stephen .. Прошу прощения за мой плохой английский. Я имел в виду всю последовательность abcd, такую ​​как a,b,c,d,ab, ac, ad, bc, bd,cd , abc, abd, acd, abcd etc, при запросе, должен возвращать подкарту с abcd -> obj1 в качестве одной из своих записей.   -  person user2436032    schedule 20.04.2015
comment
@TheLostMind da также является двухсимвольной перестановкой abcd   -  person Alex Salauyou    schedule 20.04.2015
comment
@TheLostMind Я понимаю, но ОП пояснил, что ему нужно сохранять порядок элементов, поэтому ему нужно запрашивать подпоследовательность, а не перестановку. По определению подпоследовательность получается из исходной последовательности путем удаления некоторых ее элементов с сохранением порядка остальных элементов.   -  person Alex Salauyou    schedule 20.04.2015
comment
Являются ли ключи всегда лексикографически упорядоченными строками?   -  person dting    schedule 20.04.2015
comment
@StephenC Судя по этому (planetmath.org/subsequence), OP абсолютно прав, называя это subsequence< /я>. ac можно получить из abcd, удалив b и d.   -  person Alex Salauyou    schedule 20.04.2015
comment
@DTing .. нет, они могут быть для любой строки, например .. Запись: good -> obj4, query = god, должна возвращать good -> obj4 в качестве одной из своих записей.   -  person user2436032    schedule 20.04.2015
comment
@Sasha .. Решение, которое вы предложили, действительно возможно, но в случае длинных слов пространство, которое оно займет, увеличится в геометрической прогрессии. Пример: даже для 5-буквенного ключа количество подпоследовательностей равно 32. Для 6-буквенного ключа количество подпоследовательностей равно 64. Поэтому, если я использую пробел из 64 слов только для хранения одного ключа, тогда карта не будет масштабироваться больше, чем тысяча записей в мобильной среде, где ресурсы очень ограничены.   -  person user2436032    schedule 20.04.2015
comment
Это компромисс для быстрого доступа. И вы не будете хранить 64 полных слова, только 64 String ссылки, указывающие на одно и то же значение. Размер одной записи в HashMap составляет 24 байта.   -  person Alex Salauyou    schedule 20.04.2015
comment
Индексация не потребует так много. напр. для двухбуквенного индекса HashMap нужно 26^2 * (4 + 32) = 24К байт для ключей и s^2/2*24*N для значений, где N — количество слов, s — длина слова. Для s = 6 это 768 * N байта, не так уж и много.   -  person Alex Salauyou    schedule 20.04.2015
comment
Вы не правы насчет экспоненциального роста, количество подпоследовательностей заданной длины определяется биномиальным коэффициентом, а не экспонентой. Для s = 10 и 2-символьных подпоследовательностей такое число будет s * (s - 1) / 2 = 45   -  person Alex Salauyou    schedule 20.04.2015
comment
Нет, я к тому, что у вас должно быть 2 карты: основная (ключ -> объект) и индексная (подпоследовательность -> ключ). В индексной карте для 2-символьных подпоследовательностей у вас будет 26 ^ 2 ключа, каждая запись будет занимать 24 (размер HashMap.Entry) + 4 (2 символа) + 32 (размер String) байт. И каждое ключевое слово должно быть сохранено как полная строка once, потому что индексная карта будет содержать ссылки на ключ String, а не на сам массив байтов.   -  person Alex Salauyou    schedule 20.04.2015
comment
@sasha .. Если вы ограничите двухсимвольную подпоследовательность, то, как вы указали, это будет биномиальный коэффициент. Но если мы хотим сохранить всю подпоследовательность, она будет экспоненциальной, так как будет суммой биномиальных коэффициентов от 1 до длины.   -  person user2436032    schedule 20.04.2015
comment
@user2436032 user2436032 Я думаю, что хранить все подпоследовательности бесполезно, потому что их количество действительно быстро растет. Моя основная идея: по первым 2 символам запроса вы получаете список ключей из индексной карты, затем фильтруете все ключи, чтобы они соответствовали запросу, затем собираете записи из основной карты.   -  person Alex Salauyou    schedule 20.04.2015
comment
@Sasha, сохраняя только индексную карту из 2 символов, я не смогу искать запрос подпоследовательности длиной 3 или более. например. В исходном вопросе: при запросе акк должна возвращаться только третья запись.   -  person user2436032    schedule 20.04.2015
comment
@саша. это хорошая идея сохранить подпоследовательность из 2 символов, а затем отфильтровать позже, я попробую. Спасибо   -  person user2436032    schedule 20.04.2015
comment
Вы берете результат для двухсимвольной подпоследовательности, а затем фильтруете его, чтобы он соответствовал полному запросу. Чтобы уменьшить количество ключей, используемых на первом шаге, вы можете расширить запрос на все возможные 2-символьные подпоследовательности и использовать наименее частую.   -  person Alex Salauyou    schedule 20.04.2015


Ответы (5)


Подводя итог тому, что я написал в комментариях, я бы сделал это следующим образом:

  1. Создайте дополнительный индекс HashMap с записями [двухсимвольная подпоследовательность -> список слов].
  2. При добавлении: генерировать все отдельные 2-символьные подпоследовательности из заданного слова, добавлять это слово к каждой соответствующей записи индексной карты.
  3. При запросе: генерировать все отдельные 2-символьные подпоследовательности из запроса, среди них находить ту, которая соответствует кратчайшему списку в индексной карте, брать этот список. Отфильтруйте его по полному запросу и соберите соответствующие значения с основной карты.

Если запрос состоит из одного символа, то выполнить полную проверку. Я считаю, что это будет иметь лучшее пространство/сложность, чем создание дополнительного индекса для одиночных символов, особенно когда запросы с 1 символом встречаются редко.

person Alex Salauyou    schedule 20.04.2015

Примечание. Мое решение работает в случае поиска подстроки. Читать редактировать.

Решение:

Используйте префиксную структуру данных: http://en.wikipedia.org/wiki/Trie

Вам нужно будет хранить:

abcd -> obj1

as:

abcd -> obj1
bcd -> obj1
cd -> obj1
d -> obj1

Чтобы найти свой результат, вы должны достаточно углубиться в попытку, чтобы удовлетворить условию, и оттуда найти все возможные решения.

Примеры:

ab -> obj1
cde -> obj1
bad -> obj2

Мы бы вставили следующие записи в наш Trie:

ab -> obj1
b -> obj1
cde -> obj1
de -> obj1
e -> obj1
bad -> obj2
ad -> obj2
d -> obj2

Теперь представьте, что мы ищем следующие записи:

d

Сначала переместитесь вниз для символа d в ​​Trie. После этого мы бы DFS. Мы уже находимся в obj2, и если мы перейдем к символу e в obj1. Таким образом, мы можем получить их обоих с записью d.

cd

Сначала мы переходим к персонажу c, а затем к персонажу d. Теперь мы используем DFS, и единственный путь, который мы можем найти, — это добавить символ e, который ведет к obj1.

efg

В Trie нет пути, удовлетворяющего нашему условию, поэтому мы не получаем решений.

ИЗМЕНИТЬ:

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

person Neithrik    schedule 20.04.2015
comment
что, если пользователь запрашивает cd? какой путь поиска это будет? - person Alex Salauyou; 20.04.2015
comment
Это будет наш третий входной диск. Вы бы двигались для char c и для char d. После этого вы бы выполнили DFS для всех параметров, но был бы только один — obj1. - person Neithrik; 20.04.2015
comment
Итак, для ключа abcd вам нужно сохранить целое дерево для каждого abcd, bcd, cd, d, acd, ac, abd, ab, ad...? - person Alex Salauyou; 20.04.2015
comment
Привет, Рико, ваше решение будет отлично работать для Substring , но оно не будет работать для подпоследовательности, например, если какой-то один запрос bd на приведенной выше карте не вернет запись: abcd -> obj1 поскольку нет ключа с bd в качестве префикса . - person user2436032; 20.04.2015
comment
Понимаю. В этом случае нужно было бы хранить все перестановки строки. - person Neithrik; 20.04.2015
comment
Не перестановки. Порядок важен. - person Stephen C; 20.04.2015

Ваши «символы» могут быть интерпретированы как термины в традиционном И-запросе поисковой системы.

Из этого комментария:

все подпоследовательности abcd, такие как a,b,c,d,ab, ac, ad, bc, bd,cd , abc, abd, acd, abcd и т. д., при запросе должны возвращать подкарту с abcd -> obj1 как один из его входов.

Мы могли бы интерпретировать это с документом, содержащим термины Aspid Beaver Cherokee Deer (ABCD), документ должен быть возвращен при поиске любого из его слов или любой из их комбинаций.

Для этого вам нужно построить инвертированный индекс. Подробнее см. в этом ответе. По сути, вы должны построить HashMap или таблицу поиска (символов не так много) для каждого «символа» (=термин на языке поисковой системы), где поиск вернет все objX, где он появляется (=документ на языке поисковых систем), в идеале в порядке возрастания.

Чтобы найти все объекты, связанные с набором символов, вы должны объединить наборы для каждого отдельного символа. Поскольку они упорядочены, вы можете вычислить пересечение множества за линейное время.

Если запрос ba должен не совпадать с ключом abc, вы можете уточнить таблицу поиска / HashMap для хранения позиций символов (например: сохранить «b находится в Obj1 в позиции 0», «a найдено в Obj1 в позиции 1", вместо того, чтобы отбрасывать позиции). При вычислении поискового пересечения вы будете действовать в порядке поиска и отбрасывать совпадения с неправильным относительным порядком.

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


Изменить: Производительность

Если количество отдельных «терминов» невелико (например: 26 строчных английских букв), вы можете ускорить это, добавив n-граммы в качестве терминов (где для «abc» вы должны добавить «a», «b» , «с», «аб», «ас», «бк» и «абв»). Применяются те же правила - с половиной поисков и, поскольку ожидается перекрытие, можно использовать трюк Саши, чтобы избежать хранения индексов.

person tucuxi    schedule 20.04.2015
comment
Проблема в том, что масштабирование работает против вас, если вы рисуете из алфавита из 26 символов. - person Stephen C; 20.04.2015

Вы можете попробовать aho-corasick с подстановочным знаком. Aho-Corasick — это более быстрый алгоритм сопоставления множественных шаблонов, и с подстановочным знаком он дает все подстроки. Вы можете попробовать мою реализацию php на codeplex (https://phpahocorasick.codeplex.com). Например, модульный тест с подстановочным знаком для шаблона гена:

$tree = new Ahocorasick\Ahocorasick();
$tree->add("AC");
$tree->add("GTG");
$tree->add("AACT");
echo $tree->match("ACCGAGTGCGTGGACAAACTACGATTGTGGAATGAACT","AC*GT");
$this->expectOutputString("ACCGAGT,ACCGAGTGCGT,ACCGAGTGCGTGGACAAACTACGATTGT,ACAAACTACGATTGT,ACTACGATTGT,ACGATTGT");
person Gigamegs    schedule 20.04.2015

person    schedule
comment
Привет, Вини. Решение работает правильно, но в основном это метод грубой силы (вы проверяете каждый ключ записи в наборе, чтобы определить, является ли запрос подпоследовательностью ключа или нет). Было бы лучше, если бы мы могли хранить ключи таким образом (в каком-то виде дерева), чтобы запрос можно было найти менее чем за O (n) раз. - person user2436032; 20.04.2015