diff --git a/maths/ncr_combinations.py b/maths/ncr_combinations.py new file mode 100644 index 000000000000..49add3259804 --- /dev/null +++ b/maths/ncr_combinations.py @@ -0,0 +1,77 @@ +""" +Generalized combinations (n choose r) calculator for real total and integer choose. +Wikipedia URL: +https://en.wikipedia.org/wiki/Binomial_coefficient +""" + +from math import factorial as math_factorial + + +def combinations(total: float, choose: int) -> float: + """ + Compute the number of combinations using the formula: + + combinations = total * (total-1) * ... * (total-choose+1) / choose! + + Parameters + ---------- + total : float + Total number of items. Can be any real number. + choose : int + Number of items to select. Must be a non-negative integer. + + Returns + ------- + float + The number of combinations. + + Raises + ------ + ValueError + If choose is not a non-negative integer. + + Examples + -------- + >>> combinations(5, 2) + 10.0 + >>> combinations(5.5, 2) + 12.375 + >>> combinations(10, 0) + 1.0 + >>> combinations(0, 0) + 1.0 + >>> combinations(5, -1) + Traceback (most recent call last): + ... + ValueError: choose must be a non-negative integer + >>> combinations(5, 2.5) + Traceback (most recent call last): + ... + ValueError: choose must be a non-negative integer + """ + if not isinstance(choose, int) or choose < 0: + raise ValueError("choose must be a non-negative integer") + + if choose == 0: + return 1.0 + + numerator = 1.0 + for i in range(choose): + numerator *= total - i + + denominator = math_factorial(choose) + return numerator / denominator + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example usage + total_input = float(input("Enter total (real number): ").strip() or 0) + choose_input = int(input("Enter choose (non-negative integer): ").strip() or 0) + print( + f"combinations({total_input}, {choose_input}) = " + f"{combinations(total_input, choose_input)}" + )