Two lists – find all permutations – even multiple to one – Round 2
Question:
I asked this question previously, but things changed so new question is in order.
I have two lists and I need to find all permutations.
The kicker is that multiple "number" items (as per example below) can be assigned. No assignments are also allowed.
So this:
names = ['a', 'b']
numbers = [1, 2]
Would become this:
[
"not only this"
{'a': [1], 'b': [2]},
{'a': [2], 'b': [1]},
"but also"
{'a': [], 'b': []},
{'a': [1], 'b': []},
{'a': [2], 'b': []},
{'a': [1, 2], 'b': []},
{'a': [2, 1], 'b': []},
{'a': [], 'b': [1]},
{'a': [], 'b': [2]},
{'a': [], 'b': [1, 2]},
{'a': [], 'b': [2, 1]},
]
The difference to my previous question is that in this case the list order DOES in face matter, so [1, 2] != [2, 1]
– both cases need to be encompassed.
Also, the lists above are only for illustration purposes, since in the real scenario I will be using classes:
class sample_class():
def __init__(self, parameter_1, parameter_2):
self.parameter_1 = parameter_1
self.parameter_2 = parameter_2
first_group_example_list = [sample_class(x, x+1) for x in range(2)]
second_group_example_list = [sample_class(x+5, x+5+1) for x in range(2)]
Now, since the number of permutations gets very high very quickly, speed is king. Not sure if using classes compared to list of ints has any effect on speed, but I suspect so, so if there are any good practices when it comes to that, please let me know.
Not sure if it has any effect on speed / memory, but since the lists are ordered, I was thinking about using element indexes when generating the permutations, instead of the classes themselves, and pull the actual class instances only in the subsequent processing steps.
EDIT: Let’s not complicate things with the classes and stick with names and numbers (as long the solution doesn’t care what type the two input lists are (either ints or strings)). I will figure out the rest, hopefully, as I go.
EDIT2: HERE is the solution that will calculate the number of "permutations".
import math
numbers = 6 # length of numbers list
names = 7 # length of names list
result = 0
for j in range(numbers + 1):
result += math.factorial(j + names - 1) / (math.factorial(j) * math.factorial(numbers - j))
result *= math.factorial(numbers) / math.factorial(names - 1)
print(result) # number of "permutations"
Answers:
Here’s a fast solution. Note that it doesn’t do any searching – it simply rolls the solutions out:
import itertools
def perm_lists(d, names, ix):
if ix == len(names):
yield d.copy()
return
name = names[ix]
for m in itertools.permutations(d[name]):
d[name] = m
yield from perm_lists(d, names, ix+1)
def f(p, names, numbers):
d = {name : [] for name in names}
for i, j in enumerate(p):
if j >= 0:
d[names[j]].append(numbers[i])
yield from perm_lists(d, names, 0)
def val_gen(names, numbers):
p_iter = itertools.product(range(-1, len(names)), repeat=len(numbers))
for p in p_iter:
yield from f(p, names, numbers)
This implements a generator, so you don’t have to pay the cost of the entire top-level list if all you want to do is iterate over the values, but you can of course convert it to a list if desired.
Also note that this uses tuples in place of the value lists. These are more memory-efficient than lists, and fall out naturally from the implementation, but it’s trivial to convert them to lists if needed.
Here’s an example:
names = ['a', 'b']
numbers = [1, 2]
for d in val_gen(names, numbers):
print(d)
This produces:
{'a': (), 'b': ()}
{'a': (2,), 'b': ()}
{'a': (), 'b': (2,)}
{'a': (1,), 'b': ()}
{'a': (1, 2), 'b': ()}
{'a': (2, 1), 'b': ()}
{'a': (1,), 'b': (2,)}
{'a': (), 'b': (1,)}
{'a': (2,), 'b': (1,)}
{'a': (), 'b': (1, 2)}
{'a': (), 'b': (2, 1)}
If you want the value lists to be lists rather than tuples, just change:
d[name] = m
to:
d[name] = list(m)
in perm_lists
. This will produce:
{'a': [], 'b': []}
{'a': [2], 'b': []}
{'a': [], 'b': [2]}
{'a': [1], 'b': []}
{'a': [1, 2], 'b': []}
{'a': [2, 1], 'b': []}
{'a': [1], 'b': [2]}
{'a': [], 'b': [1]}
{'a': [2], 'b': [1]}
{'a': [], 'b': [1, 2]}
{'a': [], 'b': [2, 1]}
If you want to create the full top-level list, rather than using the generator to iterate over it, you can use:
full_list = list(val_gen(names, numbers))
I suggest doing some timing experiments. I believe this will be fast when used on large amounts of data.
I asked this question previously, but things changed so new question is in order.
I have two lists and I need to find all permutations.
The kicker is that multiple "number" items (as per example below) can be assigned. No assignments are also allowed.
So this:
names = ['a', 'b']
numbers = [1, 2]
Would become this:
[
"not only this"
{'a': [1], 'b': [2]},
{'a': [2], 'b': [1]},
"but also"
{'a': [], 'b': []},
{'a': [1], 'b': []},
{'a': [2], 'b': []},
{'a': [1, 2], 'b': []},
{'a': [2, 1], 'b': []},
{'a': [], 'b': [1]},
{'a': [], 'b': [2]},
{'a': [], 'b': [1, 2]},
{'a': [], 'b': [2, 1]},
]
The difference to my previous question is that in this case the list order DOES in face matter, so [1, 2] != [2, 1]
– both cases need to be encompassed.
Also, the lists above are only for illustration purposes, since in the real scenario I will be using classes:
class sample_class():
def __init__(self, parameter_1, parameter_2):
self.parameter_1 = parameter_1
self.parameter_2 = parameter_2
first_group_example_list = [sample_class(x, x+1) for x in range(2)]
second_group_example_list = [sample_class(x+5, x+5+1) for x in range(2)]
Now, since the number of permutations gets very high very quickly, speed is king. Not sure if using classes compared to list of ints has any effect on speed, but I suspect so, so if there are any good practices when it comes to that, please let me know.
Not sure if it has any effect on speed / memory, but since the lists are ordered, I was thinking about using element indexes when generating the permutations, instead of the classes themselves, and pull the actual class instances only in the subsequent processing steps.
EDIT: Let’s not complicate things with the classes and stick with names and numbers (as long the solution doesn’t care what type the two input lists are (either ints or strings)). I will figure out the rest, hopefully, as I go.
EDIT2: HERE is the solution that will calculate the number of "permutations".
import math
numbers = 6 # length of numbers list
names = 7 # length of names list
result = 0
for j in range(numbers + 1):
result += math.factorial(j + names - 1) / (math.factorial(j) * math.factorial(numbers - j))
result *= math.factorial(numbers) / math.factorial(names - 1)
print(result) # number of "permutations"
Here’s a fast solution. Note that it doesn’t do any searching – it simply rolls the solutions out:
import itertools
def perm_lists(d, names, ix):
if ix == len(names):
yield d.copy()
return
name = names[ix]
for m in itertools.permutations(d[name]):
d[name] = m
yield from perm_lists(d, names, ix+1)
def f(p, names, numbers):
d = {name : [] for name in names}
for i, j in enumerate(p):
if j >= 0:
d[names[j]].append(numbers[i])
yield from perm_lists(d, names, 0)
def val_gen(names, numbers):
p_iter = itertools.product(range(-1, len(names)), repeat=len(numbers))
for p in p_iter:
yield from f(p, names, numbers)
This implements a generator, so you don’t have to pay the cost of the entire top-level list if all you want to do is iterate over the values, but you can of course convert it to a list if desired.
Also note that this uses tuples in place of the value lists. These are more memory-efficient than lists, and fall out naturally from the implementation, but it’s trivial to convert them to lists if needed.
Here’s an example:
names = ['a', 'b']
numbers = [1, 2]
for d in val_gen(names, numbers):
print(d)
This produces:
{'a': (), 'b': ()}
{'a': (2,), 'b': ()}
{'a': (), 'b': (2,)}
{'a': (1,), 'b': ()}
{'a': (1, 2), 'b': ()}
{'a': (2, 1), 'b': ()}
{'a': (1,), 'b': (2,)}
{'a': (), 'b': (1,)}
{'a': (2,), 'b': (1,)}
{'a': (), 'b': (1, 2)}
{'a': (), 'b': (2, 1)}
If you want the value lists to be lists rather than tuples, just change:
d[name] = m
to:
d[name] = list(m)
in perm_lists
. This will produce:
{'a': [], 'b': []}
{'a': [2], 'b': []}
{'a': [], 'b': [2]}
{'a': [1], 'b': []}
{'a': [1, 2], 'b': []}
{'a': [2, 1], 'b': []}
{'a': [1], 'b': [2]}
{'a': [], 'b': [1]}
{'a': [2], 'b': [1]}
{'a': [], 'b': [1, 2]}
{'a': [], 'b': [2, 1]}
If you want to create the full top-level list, rather than using the generator to iterate over it, you can use:
full_list = list(val_gen(names, numbers))
I suggest doing some timing experiments. I believe this will be fast when used on large amounts of data.