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.
Now I want to permutate this struct the following way:
False
don't change (disappear) 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.