Алгоритм возврата всех возможных комбинаций n объектов, взятых из разных бинов - PullRequest
3 голосов
/ 14 марта 2011

Чтобы сделать его более конкретным, мне нужен алгоритм (рекурсивный или нет), который, учитывая целое число n и матрицу в качестве входных данных, вернет мне все комбинации, которые будут иметь: 1) как минимум 1 объект из каждой строки 2) Всего будет n объектов

Мне кажется, я мог бы решить эту проблему проще, если бы я просто попробовал все комбинации и использовал те, которые имеют n объектов и по 1 в каждой строке, но я считаю, что алгоритм может быть намного более эффективным, чем этот. Я также успешно закодировал алгоритм, который будет возвращать все комбинации по 1 объекту на строку, но не смог расширить его до большего. Я программировал на Python, но любой язык в порядке. Дополнительные баллы за то, что python передает объекты по ссылке. =)

Предположим, матрица в квадрате. Если кто-то хочет знать почему, это часть более сложного алгоритма графа, который я пытаюсь решить.

Спасибо всем!

Ответы [ 4 ]

2 голосов
/ 14 марта 2011

Предположим, матрица m представляет собой список списков;Вот функция Ракетки для этого:

(define (combinations m n)
  (cond
    ((and (zero? n) (null? m)) '(()))
    ((zero? n) '())
    ((null? m) '())
    ((null? (car m)) '())
    (else
      (append (combinations (cons (cdar m) (cdr m)) n)
              (map (lambda (ls) (cons (caar m) ls))
                   (append
                     (combinations (cons (cdar m) (cdr m)) (sub1 n))
                     (combinations (cdr m) (sub1 n))))))))
1 голос
/ 22 марта 2011

Спасибо за все ответы, они были близки к тому, что я искал. Мне удалось сделать это в Python (поэтому я не проверял результаты, опубликованные здесь), моя проблема была в том, что Python передавал ссылку vs copy в вызовах функций. Я думал, что мелкой копии было бы достаточно, но, видимо, мне нужна была глубокая копия (я не продумал, почему мелкой недостаточно).

Вот как я это сделал:
PS1: Графики здесь являются словарями списков. n_edges - количество ребер, выбранных на графике
PS2: Расчет размера здесь довольно неэффективен, пока не потратили время на его исправление.
PS3: Для того, чтобы упорядочить итерацию по словарю, я создал два списка: представление списка-списка графа (lol) и индексного массива, который ему соответствует (lolindex).
PS4: Адаптированный под вопрос, который я разместил, реальный метод, который я использовал, имеет более специфический код проблемы Не проверял код в том виде, как я его здесь изложил.

def pickEdges(n_edges, newgraph):
    size = sum(len(v) for v in newgraph.itervalues())
    if (size == n_edges):
        print newgraph
        return

    for i in range(len(lol)):
        for j in range(len(lol[i])):
                tmpgraph = copy.deepcopy(newgraph)
                if (lol[i][j] not in tmpgraph[lolindex[i]]):
                       tmpgraph[lolindex[i]].append(lol[i][j])
                       pickEdges(n_edges, tmpgraph)
0 голосов
/ 20 ноября 2013

Какой замечательный вопрос!Чтобы соответствовать терминологии, я буду ссылаться на строки в вашей матрице как «входные данные сумки », а элементы во входных пакетах - как «объекты».Сумка (или мультимножество) - это контейнер, который позволяет членам появляться более одного раза, но у которых нет внутреннего порядка (в отличие от списков).

Мы ищем функцию со следующей подписью:

set of valid combinations =
generate_combinations(list of input bags, number of objects in valid combination)

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

Действительный результат должен удовлетворять следующемутребования:

  1. Минимум 1 объект из каждой входной сумки
  2. Всего будет n объектов

Я предполагаю следующее:

  1. Порядок объектов в результате не имеет значения
  2. Входная сумка может содержать дубликаты объектов
  3. Две входные сумки могут содержать дубликаты объектов (в вырожденном случае два входных пакетаможет быть идентичным)
  4. Действительный результат извлекает объекты без замены

Требование № 1 и Предположение № 4 подразумевают number of input bags <= n <= total number of objects in all bags

Data Structures

  • Поскольку входные пакеты могут содержать повторяющиеся значения (согласно предположению № 2), мы не можем использовать set / frozenset для их хранения.Списки Python подходят для этой задачи.Альтернативным контейнером может быть collection.Counter , который имеет тест членства в постоянном времени и лучшую пространственную эффективность для списков с множеством дубликатов.
  • Каждая допустимая комбинация также является сумкой
  • Заказ не имеет значения для списка входных пакетов, поэтому его можно обобщить как пакет входных пакетов.Для здравого смысла я оставлю все как есть.

Я буду использовать встроенный в Python модуль itertools для генерации комбинаций, который был представлен в v2.6.Если вы используете старую версию Python, используйте рецепт.Для этих примеров кода я неявно преобразовал генераторы в списки для ясности.

>>> import itertools, collections
>>> input_bags = [Bag([1,2,2,3,5]), Bag([1,4,5,9]), Bag([12])]
>>> output_bag_size = 5
>>> combos = generate_combinations(input_bags, output_bag_size)
>>> combos.next() #an arbitrary example
Bag([1,1,2,4,12])

Поймите, что проблема, как указано выше, может быть уменьшена до проблемы, которая немедленно решается встроенным в Python модулем itertools, которыйгенерирует комбинации последовательностей.Единственная модификация, которую нам нужно сделать, это исключить Требование № 1.Чтобы решить уменьшенные проблемы, объедините элементы сумок в одну сумку.

>>> all_objects = itertools.chain.from_iterable(input_bags)
>>> all_objects
generator that returns [1, 2, 2, 3, 5, 1, 4, 5, 9, 12]
>>> combos = itertools.combinations(all_objects, output_bag_size)
>>> combos
generator that returns [(1,2,2,3,5), (1,2,2,3,1), (1,2,2,3,4), ...]

Чтобы восстановить требование № 1, каждая действительная комбинация (выходная сумка) должна содержать 1 элемент из каждой входной сумки плюс дополнительныеэлементы из любого из пакетов, пока размер выходного пакета не достигнет указанного пользователем значения.Если перекрытие между [1 элементом из каждого входного пакета] и [дополнительными элементами из любого из пакетов] игнорируется, решение представляет собой просто декартово произведение возможных комбинаций первой и возможных комбинаций второй.

Чтобы справиться с перекрытием, удалите элементы из [1 элемента из каждого входного пакета] из [дополнительных элементов из любого из пакетов] и удалите петлю.

>>> combos_with_one_element_from_each_bag = itertools.product(*input_bags)
>>> for base_combo in combos_with_one_element_from_each_bag:
>>>     all_objects = list(itertools.chain.from_iterable(input_bags))
>>>     for seen in base_combo:
>>>         all_objects.remove(seen)
>>>     all_objects_minus_base_combo = all_objects
>>>     for remaining_combo in itertools.combinations(all_objects_minus_base_combo, output_bag_size-len(base_combo)):
>>>         yield itertools.chain(base_combo, remaining_combo)

Операция удаленияподдерживается в списках, но не поддерживается в генераторах.Чтобы избежать сохранения всех объектов в памяти в виде списка, создайте функцию, которая пропускает элементы в base_combo.

>>> def remove_elements(iterable, elements_to_remove):
>>>     elements_to_remove_copy = Bag(elements_to_remove) #create a soft copy
>>>     for item in iterable:
>>>         if item not in elements_to_remove_copy:
>>>             yield item
>>>         else:
>>>             elements_to_remove_copy.remove(item)

Реализация класса Bag может выглядеть следующим образом:

>>> class Bag(collections.Counter):
>>>     def __iter__(self):
>>>         return self.elements()
>>>     def remove(self, item):
>>>         self[item] -= 1
>>>         if self[item] <= 0:
>>>             del self[item]

Полный код

import itertools, collections

class Bag(collections.Counter):
    def __iter__(self):
        return self.elements()
    def remove(self, item):
        self[item] -= 1
        if self[item] <= 0:
            del self[item]
    def __repr__(self):
        return 'Bag(%s)' % sorted(self.elements())


def remove_elements(iterable, elements_to_remove):
    elements_to_remove_copy = Bag(elements_to_remove) #create a soft copy
    for item in iterable:
        if item not in elements_to_remove_copy:
            yield item
        else:
            elements_to_remove_copy.remove(item)

def generate_combinations(input_bags, output_bag_size):
    combos_with_one_element_from_each_bag = itertools.product(*input_bags)
    for base_combo in combos_with_one_element_from_each_bag:
        all_objects_minus_base_combo = remove_elements(itertools.chain.from_iterable(input_bags), base_combo)
        for remaining_combo in itertools.combinations(all_objects_minus_base_combo, output_bag_size-len(base_combo)):
            yield Bag(itertools.chain(base_combo, remaining_combo))

input_bags = [Bag([1,2,2,3,5]), Bag([1,4,5,9]), Bag([12])]
output_bag_size = 5
combos = generate_combinations(input_bags, output_bag_size)
list(combos)

Завершите это, добавив код проверки ошибок (такой как output_bag_size> = len (input_bags).

Преимущества этого подхода: 1. Меньше кода для поддержки (а именно itertools) 2. Нет рекурсии. Производительность Python снижается с высотой стека вызовов, поэтому минимизация рекурсии выгодна 3. Минимальное потребление памяти. Этот алгоритм требует постоянной памяти, превышающей то, что используется спискомвходные пакеты.

0 голосов
/ 14 марта 2011

Скорее всего, вы хотите изменить задачу и перечислить все комбинации простого списка? Ниже приведена функция Javascript, которая сделает это за вас:

function getWayStr(curr) {
  var nextAbove = -1;
  for (var i = curr + 1; i < waypoints.length; ++i) {
    if (nextAbove == -1) {
      nextAbove = i;
    } else {
      wayStr.push(waypoints[i]);
      wayStr.push(waypoints[curr]);
    }
  }
  if (nextAbove != -1) {
    wayStr.push(waypoints[nextAbove]);
    getWayStr(nextAbove);
    wayStr.push(waypoints[curr]);
  }
 } 
...