Вычисление подмножества списка целых чисел

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

Представьте, что у меня есть наборы (возможно, миллионы) целых чисел. Где каждый набор потенциально может содержать около 1000 элементов:

Set1: [1, 3, 7]
Set2: [1, 5, 8, 10]
Set3: [1, 3, 11, 14, 15]
...,
Set1000000: [1, 7, 10, 19]

Представьте себе определенный входной набор:

InputSet: [1, 7]

Теперь я хочу быстро вычислить, подмножеством которого является этот InputSet. В этом конкретном случае он должен возвращать Set1 и Set1000000.

Сейчас брутфорс занимает слишком много времени. Я также мог бы распараллелить с помощью Map/Reduce, но я ищу более разумное решение. Кроме того, в определенной степени он должен быть эффективным с точки зрения памяти. Я уже оптимизировал вычисления, используя BloomFilters, чтобы быстро исключить наборы, для которых входной набор никогда не может быть подмножеством.

Какая-нибудь умная техника, которую я упускаю?

Спасибо!


person user1943042    schedule 02.01.2013    source источник
comment
Какой язык? У вас есть пример кода?   -  person fge    schedule 02.01.2013
comment
Язык на самом деле не имеет значения (хотя Java будет предпочтительнее). Ищем концептуальное решение.   -  person user1943042    schedule 02.01.2013
comment
Если это Java, Set уже есть .containsAll(), я полагаю, вы пробовали это? Или вы действительно хотите избежать встроенных решений? Кроме того, ваши наборы всегда сортируются?   -  person fge    schedule 02.01.2013
comment
Ага. Но я не хочу выполнять миллион операций containsAll. Я хотел бы легко и быстро идентифицировать потенциальные подмножества. Можно предположить, что множества отсортированы.   -  person user1943042    schedule 02.01.2013
comment
Ну, вы сказали, что уже отфильтровали несоответствующие наборы с помощью фильтра Блума, так что...   -  person fge    schedule 02.01.2013
comment
Каков диапазон/домен элементов в наборах? Что такое кардинальность?   -  person wildplasser    schedule 02.01.2013


Ответы (4)


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

Это очень похоже на то, что делается в запросе AND при поиске инвертированного индекса в поле поиск информации.

В вашем примере у вас будет:

1 -> [set1, set2, set3, ..., set1000000]
3 -> [set1, set3]
5 -> [set2]
7 -> [set1, set7]
8 -> [set2]
...

EDIT:
В инвертированном индексе в IR для экономии места мы иногда используем d-gap, то есть сохраняем смещение между документами, а не фактическое число. Например, [2,5,10] станет [2,3,5]. Это и использование дельта-кодирования для представления чисел очень помогает, когда речь идет о пространстве.
(Конечно, есть и обратная сторона: нужно прочитать весь список, чтобы найти, есть ли в нем конкретный набор/документ, и нельзя использовать бинарный поиск, но иногда он того стоит, особенно если это разница между размещением индекса в ОЗУ или нет).

person amit    schedule 02.01.2013
comment
Я думал в том же направлении (перевернутый индекс). Единственным недостатком является то, что это примерно удваивает объем необходимой памяти для выполнения обработки. Надеялся на что-то более эффективное с точки зрения памяти... - person user1943042; 02.01.2013
comment
Вы можете сжать индекс, хэшируя ключи в инвертированном индексе и допуская коллизии, обменивая часть памяти на вычисления. В качестве крайнего примера вы можете индексировать по младшему значащему биту, чтобы у вас был список наборов, содержащих нечетные числа, и другой список наборов, содержащих четные числа. - person Phil Frost; 02.01.2013
comment
И теперь, когда я больше об этом думаю, не является ли то, что я предлагаю, эквивалентом фильтра Блума? - person Phil Frost; 02.01.2013
comment
@user1943042 user1943042 Вам часто нужно знать, что представляют собой оригинальные наборы? Если нет, вы можете просто не хранить их вообще (и теперь вы не удвоили использование памяти), и теперь восстановление исходных наборов становится проблемой, которую вы пытаетесь решить сейчас. - person Phil Frost; 02.01.2013
comment
@PhilFrost: это действительно похоже на это. Кроме того, оптимизация, которая часто используется в инвертированном индексе в IR, сохраняет только смещение (например, [2,5,10] станет [2,3,5]). Это называется d-gaps. Для разреженных данных это делается с использованием дельта-кодирования. для представления чисел, как правило, очень помогает. - person amit; 02.01.2013
comment
Я дал ответ, который является альтернативой этому, который должен решить хотя бы некоторые проблемы с ним. - person Nuclearman; 02.01.2013

Как насчет хранения списка наборов, содержащих каждое число?

1 -- 1, 2, 3, 1000000
3 -- 1, 3
5 -- 2
etc. 
person William Jockusch    schedule 02.01.2013

Расширяя решение Амита, вместо сохранения фактических чисел вы можете просто хранить интервалы и связанные с ними наборы.

Например, используя размер интервала 5:

 (1-5): [1,2,3,1000000]
 (6-10): [2,1000000]
 (11-15): [3]
 (16-20): [1000000]

В случае (1,7) следует рассматривать интервалы (1-5) и (5-10) (которые можно определить, просто зная размер интервала). Пересечение этих диапазонов дает вам [2,1000000]. Бинарный поиск наборов показывает, что (1,7) действительно существует в обоих наборах.

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

Вероятно, вам следует сохранить его, чтобы можно было использовать двоичный поиск для проверки значений, поэтому диапазон подмножества должен быть примерно таким (min + max)/N, где 2N — это максимальное количество значений, которые необходимо будет найти в двоичном формате. каждый набор. Например, «содержит ли набор 3 какие-либо значения от 5 до 10?» это делается путем нахождения ближайших значений к 5 (3) и 10 (11), в данном случае нет это не так. Вам нужно будет просмотреть каждый набор и выполнить двоичный поиск значений интервала, которые могут быть в наборе. Это означает, что вы не будете искать 100, когда набор достигает только 10.

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

По-прежнему будет сложно выбрать, какой диапазон использовать, слишком велик, и потребуется много времени для построения структуры данных (1000 * миллионов * log (N)). Слишком маленький, и вы начнете сталкиваться с проблемами свободного места. Идеальный размер диапазона, вероятно, таков, что он гарантирует, что количество наборов, связанных с каждым диапазоном, приблизительно равно, а также гарантирует, что общее количество диапазонов не слишком велико.

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

person Nuclearman    schedule 02.01.2013

  1. Начните поиск с наибольшего числа (7) входного набора и исключите другие подмножества (будут возвращены Set1 и Set1000000).

  2. Поиск других входных элементов (1) в оставшихся наборах.

person Kad    schedule 02.01.2013