Python: создание всех возможных комбинаций последовательностей из списка с ограничением символов

5

Мой вопрос точно такой же, как этот вопрос. У меня есть массив (список) символов. Я хотел бы получить все возможные комбинации последовательностей из этого списка, но с лимитом символов (например: максимум 2 символа). Кроме того, ни один символ не может быть повторен в строке перестановки:

chars = ['a', 'b', 'c', 'd']

# output
output = [['a', 'b', 'c', 'd'],
          ['ab', 'c', 'd'],
          ['a', 'bc', 'd'],
          ['a', 'b', 'cd'],
          ['ab', 'cd'],
          ['abc', 'd'], # this one will be exempted
          ['a', 'bcd'],  # this one will be exempted
          ['abcd']]  # this one will be exempted

Я знаю, что могу проверить условие, чтобы опустить комбинации надпредельных символов при создании и построении последовательности. Но это добавит время выполнения. Моя цель - сократить существующее время выполнения.

Без ограничения количества символов комбинации будут генерироваться как 2 ^ (N-1). Если список содержит более 15 символов, выполнение программы займет слишком много времени. Поэтому я хотел бы уменьшить количество комбинаций по лимиту.

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

  • 0
    А у тебя нет попыток показать?
  • 0
    @usr2564301 usr2564301, буквально да. Я попробовал решения из вышеупомянутого дублирующего вопроса с моими модифицированными версиями. Я даже попробовал исходный код Python itertools.combination с небольшими изменениями. Но все попытки даже не оправдали ожиданий. Извините за мою некомпетентность.
Показать ещё 2 комментария
Теги:
permutation
combinations

2 ответа

2
Лучший ответ

Один из способов сделать это - перебрать список входных данных и постепенно наращивать комбинации. На каждом шаге следующий символ берется из списка ввода и добавляется к ранее сгенерированным комбинациям.

from collections import defaultdict

def make_combinations(seq, maxlen):
    # memo is a dict of {length_of_last_word: list_of_combinations}
    memo = defaultdict(list)
    memo[1] = [[seq[0]]]  # put the first character into the memo

    seq_iter = iter(seq)
    next(seq_iter)  # skip the first character
    for char in seq_iter:
        new_memo = defaultdict(list)

        # iterate over the memo and expand it
        for wordlen, combos in memo.items():
            # add the current character as a separate word
            new_memo[1].extend(combo + [char] for combo in combos)

            # if the maximum word length isn't reached yet, add a character to the last word
            if wordlen < maxlen:
                word = combos[0][-1] + char

                new_memo[wordlen+1] = newcombos = []
                for combo in combos:
                    combo[-1] = word  # overwrite the last word with a longer one
                    newcombos.append(combo)

        memo = new_memo

    # flatten the memo into a list and return it
    return [combo for combos in memo.values() for combo in combos]

Выход:

[['a', 'b', 'c', 'd'], ['ab', 'c', 'd'], ['a', 'bc', 'd'],
 ['a', 'b', 'cd'], ['ab', 'cd']]

Эта реализация медленнее, чем наивный itertools.product для коротких входных данных:

input: a b c d
maxlen: 2
iterations: 10000

itertools.product: 0.11653625800136069 seconds
make_combinations: 0.16573870600041118 seconds

Но он быстро поднимается, когда список ввода длиннее:

input: a b c d e f g h i j k
maxlen: 2
iterations: 10000

itertools.product: 6.9087735799985240 seconds
make_combinations: 1.2037671390007745 seconds
1

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

chars = ['a', 'b', 'c', 'd']
def get_combos(c):
  if len(c) == 1:
    yield c
  else:
     yield c
     for i in range(len(c)-1):
       yield from get_combos([c[d]+c[d+1] if d == i else c[d] if d < i else c[d+1] for d in range(len(c)-1)])

final_listing = list(get_combos(chars))
last_results = list(filter(lambda x:all(len(c) < 3 for c in x), [a for i, a in enumerate(final_listing) if a not in final_listing[:i]]))

Выход:

[['a', 'b', 'c', 'd'], ['ab', 'c', 'd'], ['ab', 'cd'], ['a', 'bc', 'd'], ['a', 'b', 'cd']]

Ещё вопросы

Сообщество Overcoder
Наверх
Меню