简体   繁体   中英

How to create all permutations of a list of tuples depending on one boolean field keeping order in Python

Let's say i have an input as a list of tuples like this:

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

Every tuple which has True as second parameter is considered optional.

  • amount of tuples in the list is arbitrary
  • the value of the first value is arbitrary and must be preserved

Now I want to permutate this struct the following way:

  • the output should be a list of list of tuples
  • every list of tuples is unique
  • the lists differenciates on the optional tuples which are either there or not
  • tuples which are False don't change (disappear)
  • ordering of the tuples inside the lists does not change
  • ordering of the tuples does not change
  • lists of tuples can be in any order

The output of the example above should therefore look like this:

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

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

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

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

Any thoughts how to solve this one in an elegant way? I tried with recursion but I could not pull it off.

I'm not sure there is a particularly elegant way. Conceptually, you need to compute the power set of the optional elements, but merge it with the non-optional elements in a way that fulfils your requirements. Here is one way:

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)
    ])

printing

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

The loop iterates over all tuples indicating whether to include the optional elements:

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

The list comprehension in the print statement includes all non-optional elements, and for the optional ones looks at the next available element from include .

There is actually a nice recursive solution I just thought of:

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

This creates a lazy generator over all lists of tuples:

>>> 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)]]

You could make choices by making a copy of the list of so far made choices and append the new value to the copy and afterwards merging it with the list of so far made choices.

Sorry for that horrible explanation, but I can't come up with anything better right now. Feel free to edit, if you can come up with anything better. Otherwise I hope, below diagram and code do a better job at explaining my solution.

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

The basic idea is to iterate over ever item, clone all partial solutions and append the item to the cloned solutions.

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

Output:

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)]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM