Python: алгоритм формирования всех вариантов чисел из набора цифр

Есть набор цифр, например [0, 1, 2, 2]

Подскажите алгоритм (вообще голова отключилась :)), чтобы сформировать все возможные уникальные наборы чисел, сформированных из указанных цифр, использованных по 1 разу, при этом числа не могут содержать в начале 0 (кроме самого 0)

Т.е. нужно сделать

res = []
res.extend(itertools.permutations([0, 1, 2, 2]))
res.extend(itertools.permutations([10, 2, 2]))
res.extend(itertools.permutations([10, 22]))
res.extend(itertools.permutations([20, 1, 2]))
res.extend(itertools.permutations([20, 12]))
res.extend(itertools.permutations([20, 21]))
... и т.д.

res = list(set(res))

но полностью автоматически.


Ответы (4 шт):

Автор решения: MarianD

Например так:

from itertools import permutations

digits = [0, 1, 2, 2]
digits_as_chars = map(str, digits)

perms = permutations(digits_as_chars)

result = [int("".join(per)) for per in perms]
result = [i for i in result if i >= 10 ** (len(digits) - 1)]
result = set(result)

Результат (в переменной result):

{1022, 1202, 1220, 2012, 2021, 2102, 2120, 2201, 2210}

Объяснение:

Условие i >= 10 ** (len(digits) - 1) в последнем генераторе списка гарантирует, что число не будет начинаться с нуля т.к. например в случае 4-значного числа оно будет более или равно 1000.

→ Ссылка
Автор решения: Oopss
import itertools

s = ['0', '1', '2', '2']
res=[]
for r in range(1, len(s) + 1):
    for i in filter(lambda x: x[0] != '0', itertools.permutations(s,r)):
        res.append(''.join(i))
print(res)
res=list(set(res))
res.insert(0,0)
print(sorted(map(int,res)))

[0, 1, 2, 10, 12, 20, 21, 22, 102, 120, 122, 201, 202, 210, 212, 220, 221, 1022, 1202, 1220, 2012, 2021, 2102, 2120, 2201, 2210]
→ Ссылка
Автор решения: Stanislav Volodarskiy

Рекурсивный перебор комбинаций чисел. digits - словарь, перечисляющий цифры. Для цифр 0, 1, 2, 2 словарь будет {2: 2, 0: 1, 1: 1}.

Генератор numbers(digits) перечисляет все числа, которые можно построить из словаря digits. При перечислении словарь меняется - цифры, составляющие число, из словаря удалены. Например, когда выдаётся число 20, в словаре останется одна двойка и одна единица: {2: 1, 1: 1}. Эта особенность пригодится дальше.

Генератор combinations(digits, combination) перечисляет комбинации чисел, которые можно составить из digits. Он использует numbers(digits) для перечисления первых чисел. Хвост комбинации строится рекурсивно. При построении числа digits худеет, из него удаляются использованные цифры. Когда он опустел, комбинация готова.

combinations не публикует комбинации через yield. Вместо этого текущая комбинация записывается во второй аргумент. В цикле её можно обработать, но если вы хотите сохранить комбинацию, скопируйте её до конца итерации. На следующей итерации новая комбинация будет записана в тот же список. Так сделано для скорости.

import collections


def add(c, k):
    c[k] += 1


def remove(c, k):
    if c[k] == 1:
        del c[k]
    else:
        c[k] -= 1


def numbers(digits):

    def search(prefix):
        yield prefix
        for k in tuple(digits.keys()):
            remove(digits, k)
            yield from search(10 * prefix + k)
            add(digits, k)

    for k in tuple(digits.keys()):
        remove(digits, k)
        if k == 0:
            yield 0
        else:
            yield from search(k)
        add(digits, k)


def combinations(digits, combination):

    def search():
        if digits:
            for n in numbers(digits):
                combination.append(n)
                yield from search()
                combination.pop()
        else:
            yield

    return search()


def main():
    digits = collections.Counter(map(int, input()))
    combination = []
    for _ in combinations(digits, combination):
        print(*combination)


main()
$ echo 0122 | python combinations.py
0 1 2 2
0 1 22
0 12 2
0 122
0 2 2 1
0 2 21
0 2 1 2
0 2 12
0 22 1
0 221
0 21 2
0 212
1 2 2 0
1 2 20
1 2 0 2
1 22 0
1 220
1 20 2
1 202
1 0 2 2
1 0 22
12 2 0
12 20
12 0 2
122 0
1220
120 2
1202
10 2 2
10 22
102 2
1022
2 2 0 1
2 2 1 0
2 2 10
2 20 1
2 201
2 21 0
2 210
2 0 1 2
2 0 12
2 0 2 1
2 0 21
2 1 2 0
2 1 20
2 1 0 2
2 12 0
2 120
2 10 2
2 102
22 0 1
22 1 0
22 10
220 1
2201
221 0
2210
20 1 2
20 12
20 2 1
20 21
201 2
2012
202 1
2021
21 2 0
21 20
21 0 2
212 0
2120
210 2
2102
→ Ссылка
Автор решения: Vladimir Bogdanov

Решение получилось неочевидным, постараюсь объяснить логику работы на примерах. В общих чертах, сначала формируем все возможные комбинации из цифр исходного списка. В итоге получаем комбинации цифр из исходного списка. Далее формируем список двухзначных, трехзначных и четырехзначных чисел (10, 22, 120, 221, 2210, 1022 и т.д.). Затем нужно сформировать комбинации полученных двух- и трехзначных с цифрами из исходного списка. Например, для числа 12 формируем комбинации для оставшихся цифр 0 и 2. Для числа 102 комбинации с 2. Для числа 12 можно также сформировать комбинации из 12 и 20, для этого уже обработанный список из 0 и 2 и число 12 сохраняем в очереди для повторной обработки, но уже для комбинации чисел 12 и 20. Алгоритм работает для разных комбинаций исходных данных, например, для списков [0,1,2,2,3] или [1,2,4,7,8,9] и т.п.

from itertools import permutations
from functools import reduce
from collections import deque, Counter
from typing import NamedTuple

input_list = [0, 1, 2, 2]  #Входные данные

# Класс данных для удобства загрузки-выгрузки данных в очередь
class ComboNums(NamedTuple):
    Nums_List: list[int]
    Input_List: list[int]

res = set()
# Предварительно обрабатываем каждое отдельное значение исходного списка
# В результирующий список сохраняем все комбинации из цифр исходного списка и удаляем дубли
res = set(permutations(input_list))

# Очередь потребуется для хранения списков, требующих обработки
query_buff: deque = deque()
# Перед запуском цикла обработки загружаем в очередь исходный список
query_buff.append(ComboNums(list(), input_list))

while query_buff:
    # Пока в очереди есть что обрабатывать выгружаем список для обработки
    cnum: ComboNums = query_buff.pop()
    # Формируем генератор, который из цифр списка составляет двухзначные, трехзначные и т.д. числа
    # Т.к. числа, состоящие из одной цифры, уже обработаны, начинаем с двухзначных чисел
    gen_digits = ( digit
        for perms in (
            set(permutations(cnum.Input_List, i)) for i in range(2,len(cnum.Input_List)+1)
        )
        for digit in perms if digit[0] != 0 # Исключаем числа начинающиеся с нуля
        )
    # Перебираем все полученные двухзначные, трехзначные и т.д. числа
    for digits_list in gen_digits:
        # Удаляем из списка цифры, из которых состоят двухзначных, трехзначных и т.д. числа.
        # Например, для числа 12 из списка удаляем цифры 1 и 2, оставив 0 и 2
        digits_count = Counter(cnum.Input_List)
        digits_count.subtract(digits_list)
        buff_list = list(digits_count.elements())
        # Из отобранных цифр (в нашем примере 1 и 2) формируем число 12
        num = reduce(lambda dig_prev, dig_next: 10 * dig_prev + dig_next, digits_list)
        # В результирующий список записываем все возможные комбинации числа 12 и оставшихся в списке цифр 0 и 2
        res.update(set(permutations(cnum.Nums_List + [num] + buff_list)))
        # Т.к. в списке остались цифры 0 и 2, из которых можно
        # сформировать число 20 и комбинации (12,20) и (20,12),
        # сохраняем список из чисел 0 и 2, а также полученное 
        # число 12 в очереди для дальнейшей обработки
        if len(buff_list) > 1:
            query_buff.append(ComboNums(cnum.Nums_List + [num], buff_list))
# Формируем список результатов и сортируем его для удобства отображения
list_res = list(res)
list_res.sort()
print(list_res)
→ Ссылка