Как создать все перестановки списка кортежей в зависимости от одного логического поля, сохраняя порядок в Python

Скажем, у меня есть входные данные в виде списка таких кортежей:

[('a', True),
 ('b', False),
 ('c', True),
 ('d', False)] 

Каждый кортеж, в котором вторым параметром указан True, считается необязательным.

  • количество кортежей в списке произвольно
  • значение первого значения произвольно и должно быть сохранено

Теперь я хочу переставить эту структуру следующим образом:

  • вывод должен быть списком списка кортежей
  • каждый список кортежей уникален
  • списки различаются по необязательным кортежам, которые либо есть, либо нет
  • кортежи, которые являются False, не изменяются (исчезают)
  • порядок кортежей внутри списков не меняется
  • порядок кортежей не меняется
  • списки кортежей могут быть в любом порядке

Следовательно, результат приведенного выше примера должен выглядеть следующим образом:

[[('a', True),
  ('b', False),
  ('c', True),
  ('d', False)],

 [('b', False),
  ('c', True),
  ('d', False)],

 [('a', True),
  ('b', False),
  ('d', False)],

 [('b', False),
  ('d', False)]]

Есть мысли, как элегантно решить эту проблему? Я пробовал использовать рекурсию, но не смог.

Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
437
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

import itertools
a = [('a', True), ('b', False), ('c', True), ('d', False)]
optional_count = sum(optional for x, optional in a)
for include in itertools.product([True, False], repeat=optional_count):
    include_iter = iter(include)
    print([
        (x, optional)
        for x, optional in a
        if not optional or next(include_iter)
    ])

печать

[('a', True), ('b', False), ('c', True), ('d', False)]
[('a', True), ('b', False), ('d', False)]
[('b', False), ('c', True), ('d', False)]
[('b', False), ('d', False)]

Цикл перебирает все кортежи, указывая, следует ли включать необязательные элементы:

True, True
True, False
False, True
False, False

Понимание списка в операторе печати включает в себя все необязательные элементы, а для дополнительных просматривается следующий доступный элемент из include.

Вы можете сделать выбор, сделав копию списка уже сделанных выборов и добавив новое значение к копии, а затем объединив его со списком уже сделанных выборов.

Извините за это ужасное объяснение, но сейчас я не могу придумать ничего лучшего. Не стесняйтесь редактировать, если сможете придумать что-нибудь получше. В противном случае я надеюсь, что приведенная ниже диаграмма и код лучше справятся с объяснением моего решения.

                                        []
                    -------------------------------------------
                   []                                        [0]
       ----------------------------                ---------------------------
       []                       [1]                [0]                  [0, 1]
       ...                      ...                ...                  ...

Основная идея состоит в том, чтобы перебрать каждый элемент, клонировать все частичные решения и добавить элемент к клонированным решениям.

def choose(arr):
    res = [[]]

    for t in arr:
        if t[1]:
            # make a copy of all found solutions
            clone = [list(c) for c in res]

            # append the new value to the original solutions
            for l in res:
                l.append(t)

            # merge solution-list with the list of copies
            res += clone
        else:
            # non-optional element => just add the element to all solutions
            for l in res:
                l.append(t)

    return res

Выход:

print('\n'.join(str(l) for l in choose([('a', True), ('b', False), ('c', True), ('d', False)])

[('a', True), ('b', False), ('c', True), ('d', False)]
[('b', False), ('c', True), ('d', False)]
[('a', True), ('b', False), ('d', False)]
[('b', False), ('d', False)]

На самом деле есть хорошее рекурсивное решение, о котором я только что подумал:

def choices(a):
    if not a:
        yield []
        return
    head, *tail = a
    if head[1]:
        yield from choices(tail)
    for tail_choice in choices(tail):
        yield [head] + tail_choice

Это создает ленивый генератор для всех списков кортежей:

>>> list(choices(a))
[[('b', False), ('d', False)],
 [('b', False), ('c', True), ('d', False)],
 [('a', True), ('b', False), ('d', False)],
 [('a', True), ('b', False), ('c', True), ('d', False)]]

Да, это похоже на решение, которое я придумал. Про оператора yield from не знал. Я тоже выложу свой. Может быть, мы сможем обсудить, какое на самом деле лучшее решение здесь, поскольку они все решают его. Хотя мне очень нравятся рекурсивные.

systderr 26.10.2018 23:20

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

Sven Marnach 26.10.2018 23:23

Ich komme übrigens aus der Nähe von Neumünster. Da fragt man in die weite Welt hinein und bekommt die Antwort von jemandem aus Preetz. :)

systderr 27.10.2018 00:15

@systderr Kleine Welt :)

Sven Marnach 27.10.2018 00:41

без надобности вычисляет choices(tail) дважды - в противном случае хороший ответ

Mulan 27.10.2018 01:28

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