Reputation: 2171
Let's say I have a list of the strings:
l = ['A','B','C','D']
I know that to generate all combinations of these with replacement, choose n
, that I would use the itertools.combinations
library method to get them.
For example,
list(combinations(l, 2))
would give me
[['A','B'], ['A','C'],['A','D'],['B','C'],['B','D'],['C','D']]
However, instead of brackets, I would like parentheses:
['(A,B)', '(A,C)','(A,D)','(B,C)','(B,D)','(C,D)']
Now, let's say I want to extend this and add in two operations for these AND
and OR
:
So that I would get ['(A','AND','B)', '(A','OR','B)',etc.]
Extend that even further, to get nested parentheses in the case where n=3
:
['((A','AND','B)', 'AND', 'C)', '((A','AND','B)', 'OR', 'C)', '((A','OR','B)', 'OR', 'C)', '((A','OR','B)', 'AND', 'C)', etc.]
With it ideally being of the form:
['((A AND B) AND C)', '((A AND B) OR C)', '((A OR B) OR C)', '((A OR B) AND C)', etc.]
So, in summary, I am choosing combinations a list n
elements at a time, with permutations on operators ['AND', 'OR'] and adding nesting from the left.
I've done something similar in JavaScript, but there it was easier, since the user would construct the actual sentence. It was not created from a set of permutations and combinations.
Upvotes: 3
Views: 458
Reputation: 106936
You can use itertools.combinations
to pick operands from the given list, use itertools.product
to produce combinations of operators, and use itertools.product
again to produce all mixes of operands and operators, and use a for
loop that keeps nesting lists according to the chosen operands and operators to build the desired output:
from itertools import combinations, product
def expressions(l, n):
for (operations, *operands), operators in product(
combinations(l, n), product(('AND', 'OR'), repeat=n - 1)):
for operation in zip(operators, operands):
operations = [operations, *operation]
yield operations
so that list(expressions(['A','B','C','D'], 3))
returns:
[[['A', 'AND', 'B'], 'AND', 'C'],
[['A', 'AND', 'B'], 'OR', 'C'],
[['A', 'OR', 'B'], 'AND', 'C'],
[['A', 'OR', 'B'], 'OR', 'C'],
[['A', 'AND', 'B'], 'AND', 'D'],
[['A', 'AND', 'B'], 'OR', 'D'],
[['A', 'OR', 'B'], 'AND', 'D'],
[['A', 'OR', 'B'], 'OR', 'D'],
[['A', 'AND', 'C'], 'AND', 'D'],
[['A', 'AND', 'C'], 'OR', 'D'],
[['A', 'OR', 'C'], 'AND', 'D'],
[['A', 'OR', 'C'], 'OR', 'D'],
[['B', 'AND', 'C'], 'AND', 'D'],
[['B', 'AND', 'C'], 'OR', 'D'],
[['B', 'OR', 'C'], 'AND', 'D'],
[['B', 'OR', 'C'], 'OR', 'D']]
Upvotes: 2
Reputation: 71461
You can use recursion with a generator:
def op(d, n, _d, c = []):
if _d == 1:
yield c
else:
for i in d:
if sum(isinstance(h, list) or h not in {'AND', 'OR'} for h in c) == n:
yield from op(d, n, _d-1, c=[c, 'OR', i])
yield from op(d, n, _d-1, c=[c, 'AND', i])
else:
if not c:
yield from op(d, n, _d, c=[i])
else:
yield from op(d, n, _d, c=[*c, 'OR', i])
yield from op(d, n, _d, c=[*c, 'AND', i])
result = list(op(['A','B','C','D'], 2, 2))
Output (first ten results):
[[['A', 'OR', 'A'], 'OR', 'A'], [['A', 'OR', 'A'], 'AND', 'A'], [['A', 'OR', 'A'], 'OR', 'B'], [['A', 'OR', 'A'], 'AND', 'B'], [['A', 'OR', 'A'], 'OR', 'C'], [['A', 'OR', 'A'], 'AND', 'C'], [['A', 'OR', 'A'], 'OR', 'D'], [['A', 'OR', 'A'], 'AND', 'D'], [['A', 'AND', 'A'], 'OR', 'A'], [['A', 'AND', 'A'], 'AND', 'A']]
This solution will enable you to control both the length and depth of each element in result
. For instance, when generating to a depth of 4
:
result = list(op(['A','B','C','D'], 2, 4))
print(result[:10])
Output:
[[[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'AND', 'A'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'OR', 'B'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'AND', 'B'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'OR', 'C'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'AND', 'C'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'OR', 'D'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'OR', 'A'], 'AND', 'D'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'AND', 'A'], 'OR', 'A'], [[[['A', 'OR', 'A'], 'OR', 'A'], 'AND', 'A'], 'AND', 'A']]
Upvotes: 2