Как генерировать перестановки с ограничениями

Я хочу создать все возможные списки ["x","y","z",1,2,3], упорядоченные по возрастанию. «x», «y» и «z» могут быть любыми действительными числами, поэтому их порядок может быть произвольным. Однако, поскольку 1<2<3, 1 должно быть раньше 2, а 2 должно быть раньше 3. Так, например, должна быть сгенерирована перестановка ["x", 1, "y", "z", 2, 3]. , но перестановка ["x", 1, "y", "z", 3, 2] генерироваться не должна.

Самое простое решение

for p in itertools.permutations(["x","y","z",1,2,3]):
    if permutation_satisfies_the_constraint(p):
        yield p

но это очень неэффективно, так как создаст много ненужных перестановок (the total number of permutations of 6 elements is 6!=720, but the total number of legal permutations is 6!/3!=120).

Каков эффективный способ генерировать только те перестановки, которые удовлетворяют ограничению на 1,2,3?

Короткий ответ: генерировать перестановки x, y, z. Затем вставьте 1,2,3 в том порядке, в котором это разрешено вашими правилами. Мне кажется, что есть только одно правильное решение вашего вопроса: вся заданная последовательность находится в порядке возрастания. Сортировка сделает это.

Martin Brown 27.07.2024 22:15

@MartinBrown для каждой перестановки x,y,z существует около 20 вариантов вставки 1,2,3 (например, 1,2,3,x,y,z; 1,2,x,3,y,z; и т. д.) Как мне эффективно генерировать все эти параметры?

Erel Segal-Halevi 27.07.2024 23:51

Создайте только одну перестановку из "x","y","z". Тогда для каждой перестановки есть 4 места, куда можно вставить целые числа (начало, конец и два промежуточных). Используйте combinations_with_replacement(range(4), 3), чтобы получить возможные позиции для вставки целых чисел (в одной позиции может быть более одного целого числа). Вставьте целые числа, их порядок уже задан ограничением.

Michael Butscher 28.07.2024 00:06

это задача дискретной математики. «с ограничениями» ==> вам следует объединить ограниченные части перед созданием перестановок, независимо от того, задействован ли компьютер. предыдущие комментарии подробно описывают, как их объединить.

Innovations Anonymous 28.07.2024 00:39

Я считаю, что Звезды и полосы — отличный инструмент для визуализации этого.

Neil 28.07.2024 03:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
5
89
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Один из подходов - переформулировать проблему выбора перестановки для вашего неограниченного набора (["x","y","z"] в вашем примере) и выбора ее позиций на выходе, при этом позиции упорядоченного списка ([1,2,3] в вашем примере) являются оставшимися .

Например, ["z",1,"x","y",2,3] — это перестановка ["z","x","y"] и позиции [0,2,3].

Вот код для более общего ответа на вашу проблему с набором текста и проверкой, где free_list — это ваш ["x","y","z"], а ordered_list — ваш [1,2,3]:

import itertools
from typing import Generator

def f(free_list: list[str], ordered_list: list[float]) -> Generator[tuple[str|float, ...], None, None]:
    assert len(set(free_list)) == len(free_list), "free_list should have unique elements"
    assert all(x < y for x,y in zip(ordered_list[:-1], ordered_list[1:])), "ordered_list should be strictly increasing"
    out_len: int = len(free_list) + len(ordered_list)
    
    # Choosing a permutation of free_list
    for free_list_permutation in itertools.permutations(range(len(free_list))):
        # Choosing its place in the output
        for free_list_index_in_out in itertools.combinations(range(out_len), len(free_list)):
            out: list[str | float] = []
            
            # Inserting ordered_list and free_list at their place 
            n_free_list_inserted: int = 0
            for i in range(out_len):
                if i in free_list_index_in_out:
                    out.append(free_list[free_list_permutation[n_free_list_inserted]])
                    n_free_list_inserted += 1
                else:
                    out.append(ordered_list[i - n_free_list_inserted])
            
            yield tuple(out)
        

result = list(f(["x", "y", "z"], [1,2,3]))
assert len(result) == 120
assert len(result) == len(set(result))  # Checking uniqueness of results
for element in result:
    assert len(element) == len(set(element))  # Checking uniqueness in one result
    only_ints = [x for x in result if isinstance(x, int)]
    assert sorted(only_ints) == only_ints  # Checking ordering

Как сказано в комментариях, существуют и другие эквивалентные переформулировки, такие как выбор перестановки, а затем выбор возрастающих (с повторениями) индексов, куда вставлять элементы упорядоченного списка.

Просматривая все наборы индексов вставки для чисел и всех перестановок букв:

from itertools import *

for I in combinations(range(6), 3):
    for [*p] in permutations('xyz'):
        *map(p.insert, I, (1, 2, 3)),
        print(p)

Вывод (сокращенно Попробуйте это онлайн!):

[1, 2, 3, 'x', 'y', 'z']
[1, 2, 3, 'x', 'z', 'y']
[1, 2, 3, 'y', 'x', 'z']
[1, 2, 3, 'y', 'z', 'x']
[1, 2, 3, 'z', 'x', 'y']
[1, 2, 3, 'z', 'y', 'x']
[1, 2, 'x', 3, 'y', 'z']
[1, 2, 'x', 3, 'z', 'y']
...
['z', 'x', 'y', 1, 2, 3]
['z', 'y', 'x', 1, 2, 3]

Тест, включающий также два решения для фильтрации, как в вашем вопросе, чтобы показать, что мы на самом деле быстрее:

 97.4 ± 0.2 μs  f1
110.1 ± 0.2 μs  Abel_Adary
217.3 ± 0.5 μs  f3
273.6 ± 0.7 μs  f2

Python: 3.12.2 (main, Jun 12 2024, 09:13:57) [GCC 14.1.1 20240522]

Код (Попробуйте это онлайн!):

def f1():
    for I in combinations(range(6), 3):
        for [*p] in permutations('xyz'):
            *map(p.insert, I, (1, 2, 3)),
            yield p

def f2():
    for p in permutations(["x","y","z",1,2,3]):
        i = p.index
        if i(1) < i(2) < i(3):
            yield p

def f3():
    for p in permutations(["x","y","z",1,2,3]):
        it = iter(p)
        1 in it
        if 2 in it and 3 in it:
            yield p

def Abel_Adary():
    return f_(["x", "y", "z"], [1,2,3])
from typing import Generator
def f_(free_list: list[str], ordered_list: list[float]) -> Generator[tuple[str|float, ...], None, None]:
    assert len(set(free_list)) == len(free_list), "free_list should have unique elements"
    assert all(x < y for x,y in zip(ordered_list[:-1], ordered_list[1:])), "ordered_list should be strictly increasing"
    out_len: int = len(free_list) + len(ordered_list)
    
    # Choosing a permutation of free_list
    for free_list_permutation in itertools.permutations(range(len(free_list))):
        # Choosing its place in the output
        for free_list_index_in_out in itertools.combinations(range(out_len), len(free_list)):
            out: list[str | float] = []
            
            # Inserting ordered_list and free_list at their place 
            n_free_list_inserted: int = 0
            for i in range(out_len):
                if i in free_list_index_in_out:
                    out.append(free_list[free_list_permutation[n_free_list_inserted]])
                    n_free_list_inserted += 1
                else:
                    out.append(ordered_list[i - n_free_list_inserted])
            
            yield tuple(out)


funcs = [f1, f2, f3, Abel_Adary]

from timeit import timeit
from statistics import mean, stdev
import sys
import random
from collections import deque
from itertools import *
import itertools

expect = None
for f in funcs:
    result = set(map(tuple, f()))
    if expect is None:
        expect = result
    else:
        print(result == expect)

times = {f: [] for f in funcs}
def stats(f):
    ts = [t * 1e6 for t in sorted(times[f])[:5]]
    return f'{mean(ts):5.1f} ± {stdev(ts):3.1f} μs '
consume = deque(maxlen=0).extend
for _ in range(100):
    random.shuffle(funcs)
    for f in funcs:
        t = timeit(lambda: consume(f()), number=10) / 10
        times[f].append(t)
for f in sorted(funcs, key=stats):
    print(stats(f), f.__name__)

print('\nPython:', sys.version)

Другие вопросы по теме