Is there a math nCr function in python?
The following program calculates nCr
in an efficient manner (compared to calculating factorials etc.)
import operator as op
from functools import reduce
def ncr(n, r):
r = min(r, n-r)
numer = reduce(op.mul, range(n, n-r, -1), 1)
denom = reduce(op.mul, range(1, r+1), 1)
return numer // denom # or / in Python 2
As of Python 3.8, binomial coefficients are available in the standard library as
math.comb
:>>> from math import comb
>>> comb(10,3)
120
Statistics: combinations in Python
Updated answer in 2023: Use the math.comb function, which exists since Python 3.8 and has gotten much faster in 3.11.
Old answer: See scipy.special.comb (scipy.misc.comb in older versions of scipy). When
exact
is False, it uses the gammaln function to obtain good precision without taking much time. In the exact case it returns an arbitrary-precision integer, which might take a long time to compute. Calculate average value of all sorted array combinations
For each position in the combination, the possible values are a subset of the list starting at the position and up to the last k-p-1 element. e.g. for combinations of 6 in 1..100, position 3 can only contain values 3..96
For each of the positon/value pairs, the number of occurrences will be the product of combinations of left side elements and combinations of right side elements.
For example, for combinations of 6 elements within a list of 1..100, the number of times 45 will appear at the third position is the combinations of 2 in 1..44 times the combinations of 3 in 46..100. So we will have C(44,2) * C(55,3) * 45 for that positon/value pair.
You can repeat this calculation for each positon/value pair to obtain a total for each position in the output combinations. Then divide these totals by the number of combinations to get the expected value:
from math import comb
def countComb(N,k):
result = [0]*k
for p in range(k): # p is count on the left
q = k-p-1 # q is count on the right
for i in range(p,len(N)-q):
left = comb(i,p) # combinations on the left >= 1
right = comb(len(N)-i-1,q) # combinations on the right >= 1
result[p] += left * right * N[i]
return result
def combProb(N,k):
Cnk = comb(len(N),k)
return [S/Cnk for S in countComb(N,k)]
Output:print(countComb([1,2,3],2)) # [4, 8]
print(combProb([1,2,3],2)) # [1.3333333333333333, 2.6666666666666665]
print(countComb([1,2,3,4,5],3)) # [15, 30, 45]
print(combProb([1,2,3,4,5],3)) # [1.5, 3.0, 4.5]
# test with large number of combinations:
print(countComb(list(range(1,301)),7))
[1521500803497675, 3043001606995350, 4564502410493025,
6086003213990700, 7607504017488375, 9129004820986050,
10650505624483725]
print(combProb(list(range(1,301)),7))
[37.625, 75.25, 112.875, 150.5, 188.125, 225.75, 263.375]
Unique combinations of dice
You can use itertools.combinations_with_replacement()
in Python:
from itertools import combinations_with_replacement
options = list(range(1, 7))
print(list(combinations_with_replacement(options, 5)))
All possible combinations of elements in a list which are next to each other
You can use a recursive generator function:
stuff = [1, 2, 3, 4]
def get_combos(d, c = []):
yield tuple(c)
if d:
yield from get_combos(d[1:], c+[d[0]])
yield from get_combos(d[1:], [d[0]])
result = set(filter(None, get_combos(stuff)))
Output:{(1,),
(2,),
(3,),
(4,),
(1, 2),
(2, 3),
(3, 4),
(1, 2, 3),
(2, 3, 4),
(1, 2, 3, 4)}
All possible combinations of operations on list of numbers to find a specific number
You could use permutations from the itertools module to arrange numbers and operators in all possible ways into a string formula. Then use eval() to compute the result.
For example:
from itertools import permutations
numbers = ["1","9","8","2"]
target = 24
operators = ["+","-","*","/"]
for values in permutations(numbers,len(numbers)):
for oper in permutations(operators,len(numbers)-1):
formula = "".join(o+v for o,v in zip([""]+list(oper),values))
if eval(formula) == target: print(formula,"=",target)
[UPDATE1] If you are allowed to use the same operator more than once (as suggested by your comment on 1+1+1*8=24), you will need to use combinations_with_replacement to generate more operator patterns:from itertools import permutations,combinations_with_replacement
numbers = ["1","1","1","8"]
target = 10
operators = ["+","-","*","/"]
seen = set()
for values in permutations(numbers,len(numbers)):
for operCombo in combinations_with_replacement(operators,len(numbers)-1):
for oper in permutations(operCombo,len(numbers)-1):
formula = "".join(o+v for o,v in zip([""]+list(oper),values))
if formula not in seen and eval(formula) == target:
print(formula,"=",target)
seen.add(formula)
Essentially, this only differs from the previous example by the insertion of the for operCombo in ...
loop.Note: The combinations will generate formulas that look exactly the same so you will want to avoid printing solutions that have already been seen (as I did here). Duplications would also occur in the previous example if any numbers were repeated in the input.
Also note that in order for 9-1+8*2 to result in 24, the multiplication must be performed before additions and subtractions (i.e. under precedence rules) otherwise 9-1+8*2=32. You would need to support parentheses to cover different orders of operation.
[UPDATE2] Supporting parentheses is a bit more involved depending on how many numbers you want to allow. For 4 numbers, there are 11 patterns:
- No parentheses: A+B+C+D
- A+B group: (A+B)+C+D
- B+C group: A+(B+C)+D
- C+D group: A+B+(C+D)
- A+B and C+D groups: (A+B)+(C+D)
- A+B+C group: (A+B+C)+D
- B+C+D group: A+(B+C+D)
- A+B group + C: ((A+B)+C)+D
- A + group B+C: (A+(B+C))+D
- B+C group + D: A+((B+C)+D)
- B + group C+D: A+(B+(C+D))
Here's an example (for 4 numbers):
from itertools import permutations,combinations_with_replacement
numbers = ["9","8","1","2"]
target = 24
operators = ["+","-","*","/"]
groups = ['X+X+X+X', 'X+X+(X+X)', 'X+(X+X)+X', '(X+X+X)+X', '(X+X)+X+X', 'X+(X+X+X)', '((X+X)+X)+X', 'X+(X+(X+X))', 'X+((X+X)+X)', '(X+X)+(X+X)', '(X+(X+X))+X']
seen = set()
for values in permutations(numbers,len(numbers)):
for operCombo in combinations_with_replacement(operators,len(numbers)-1):
for oper in permutations(operCombo,len(numbers)-1):
formulaKey = "".join(oper+values)
if formulaKey in seen: continue # ignore variations on parentheses alone
for pattern in groups:
formula = "".join(o+p for o,p in zip([""]+list(oper), pattern.split("+")))
formula = "".join(v+p for v,p in zip([""]+list(values),formula.split("X")))
try:
if eval(formula) == target:
print(formula,"=",target)
seen.add(formulaKey)
break
except: pass
Groupings could result in divisions by zero, so a try:except block had to be added.This produces the following result:
9*8/(1+2) = 24
9+8*2-1 = 24
9*8/(2+1) = 24
9-1+8*2 = 24
9-(1-8*2) = 24
9-1+2*8 = 24
(9-1)*2+8 = 24
9/(1+2)*8 = 24
9/((1+2)/8) = 24
9-(1-2*8) = 24
9+2*8-1 = 24
9/(2+1)*8 = 24
9/((2+1)/8) = 24
8+(9-1)*2 = 24
8*9/(1+2) = 24
8*9/(2+1) = 24
8-(1-9)*2 = 24
8/(1+2)*9 = 24
8/((1+2)/9) = 24
8+2*(9-1) = 24
8*2+9-1 = 24
8*2-1+9 = 24
8/(2+1)*9 = 24
8/((2+1)/9) = 24
8-2*(1-9) = 24
8*2-(1-9) = 24
2*(9-1)+8 = 24
2*8+9-1 = 24
2*8-1+9 = 24
2*8-(1-9) = 24
To generate the parentheses grouping patterns for more numbers, you can use this function:from itertools import product
import re
def groupPatterns(count,pattern=None):
arr = pattern or "X"*count
if len(arr) < 2 : return [arr]
result = []
for mid in range(1,len(arr)):
leftPattern = groupPatterns(count,arr[:mid])
rightPattern = groupPatterns(count,arr[mid:])
for left,right in product(leftPattern,rightPattern):
result += [left + right]
if len(left) > 1 : result += ["(" + left + ")" + right]
if len(right) > 1 : result += [left + "(" + right + ")"]
if len(left) > 1 and len(right) > 1:
result += ["(" + left + ")(" + right + ")"]
if pattern: return result # recursion
patterns = [] # final, add "+" between X value placeholders or groups
for pat in sorted(set(result),key=lambda x:len(x)):
pat = re.sub("X(?=X)", r"X+", pat) # XX --> X+X
pat = re.sub("X\(", r"X+(", pat) # X( --> X+(
pat = re.sub("\)X", r")+X", pat) # )X --> )+X
pat = re.sub("\)\(", r")+(", pat) # )( --> )+(
patterns.append(pat)
return patterns
And then replace groups = ["X+X+X+X",...
with groups = groupPatterns(len(numbers))
in the previous example.OR, create a completely generic function for any number of values, with or without grouping and operator reuse:
from itertools import permutations,combinations_with_replacement
def numbersToTarget(numbers,target,reuseOper=True,allowGroups=True,operators=["+","-","*","/"]):
groups = groupPatterns(len(numbers)) if allowGroups else [ "+".join("X"*len(numbers)) ]
seen = set()
for values in permutations(numbers,len(numbers)):
for operCombo in combinations_with_replacement(operators,len(numbers)-1) if reuseOper else [operators]:
for opers in permutations(operCombo,len(numbers)-1):
formulaKey = str(opers)+str(values)
if formulaKey in seen: continue # ignore variations on parentheses alone
for pattern in groups:
formula = "".join(o+p for o,p in zip([""]+list(opers), pattern.split("+")))
formula = "".join(str(v)+p for v,p in zip([""]+list(values),formula.split("X")))
try:
if eval(formula) == target:
seen.add(formulaKey)
yield formula
break
except: pass
for formula in numbersToTarget([9,8,1,2],24):
print("24 =",formula)
for formula in numbersToTarget([9,8,1,2,5],0,allowGroups=False):
print("0 =",formula)
Related Topics
Writing List of Strings to Excel CSV File in Python
Dump to JSON Adds Additional Double Quotes and Escaping of Quotes
Pandas Dataframe Aggregate Function Using Multiple Columns
In Python, Why Is List[] Automatically Global
Passing Double Quote Shell Commands in Python to Subprocess.Popen()
Pip: How to Install a Git Pull Request
Changing Variable Names with Python for Loops
Python: Give Start and End of Week Data from a Given Date
How to Strip All Whitespace from String
How to Concatenate Element-Wise Two Lists in Python
Powersets in Python Using Itertools
Subclassing Python Dictionary to Override _Setitem_
Syntaxerror: Unexpected Eof While Parsing
How to Change a Widget's Font Style Without Knowing the Widget's Font Family/Size
Plotting Multiple Lines, in Different Colors, with Pandas Dataframe