Skip to content

Commit f3f6d14

Browse files
williamfisetclaude
andcommitted
Rename NChooseRModPrime to BinomialCoefficientModPrime, add tests
Rename for clarity, add input validation, clean up Javadoc, remove main() and inline test helper. Add 10 JUnit 5 tests including BigInteger reference validation and edge cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 30068c9 commit f3f6d14

File tree

5 files changed

+147
-70
lines changed

5 files changed

+147
-70
lines changed

src/main/java/com/williamfiset/algorithms/math/BUILD

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,6 @@ java_binary(
6363
runtime_deps = [":math"],
6464
)
6565

66-
# bazel run //src/main/java/com/williamfiset/algorithms/math:NChooseRModPrime
67-
java_binary(
68-
name = "NChooseRModPrime",
69-
main_class = "com.williamfiset.algorithms.math.NChooseRModPrime",
70-
runtime_deps = [":math"],
71-
)
72-
7366
# bazel run //src/main/java/com/williamfiset/algorithms/math:PrimeFactorization
7467
java_binary(
7568
name = "PrimeFactorization",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Computes the binomial coefficient C(n, r) mod p using Fermat's Little Theorem.
3+
*
4+
* Given a prime p, the binomial coefficient C(n, r) = n! / (r! * (n-r)!) can be computed modulo p
5+
* by precomputing factorials mod p and using modular inverses for the denominator. Fermat's Little
6+
* Theorem gives a^(p-1) ≡ 1 (mod p) for prime p, so the modular inverse of x is x^(p-2) mod p.
7+
* Here we use the extended Euclidean algorithm via ModularInverse instead.
8+
*
9+
* Requires p to be prime so that modular inverses exist for all non-zero values mod p, and n < p
10+
* so that factorials are non-zero mod p.
11+
*
12+
* Time Complexity: O(n) for factorial precomputation, O(log(p)) for each modular inverse.
13+
*
14+
* @author Rohit Mazumder, mazumder.rohit7@gmail.com
15+
*/
16+
package com.williamfiset.algorithms.math;
17+
18+
public class BinomialCoefficientModPrime {
19+
20+
/**
21+
* Computes C(n, r) mod p.
22+
*
23+
* @param n total items (must be >= 0 and < p).
24+
* @param r items to choose (must be >= 0 and <= n).
25+
* @param p a prime modulus.
26+
* @return C(n, r) mod p.
27+
* @throws IllegalArgumentException if parameters are out of range.
28+
*/
29+
public static long compute(int n, int r, int p) {
30+
if (n < 0 || r < 0 || r > n)
31+
throw new IllegalArgumentException("Requires 0 <= r <= n, got n=" + n + ", r=" + r);
32+
if (p <= 1)
33+
throw new IllegalArgumentException("Modulus p must be > 1, got p=" + p);
34+
35+
if (r == 0 || r == n)
36+
return 1;
37+
38+
long[] factorial = new long[n + 1];
39+
factorial[0] = 1;
40+
for (int i = 1; i <= n; i++)
41+
factorial[i] = factorial[i - 1] * i % p;
42+
43+
return factorial[n]
44+
% p * ModularInverse.modInv(factorial[r], p)
45+
% p * ModularInverse.modInv(factorial[n - r], p)
46+
% p;
47+
}
48+
}

src/main/java/com/williamfiset/algorithms/math/NChooseRModPrime.java

Lines changed: 0 additions & 63 deletions
This file was deleted.

src/test/java/com/williamfiset/algorithms/math/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,14 @@ java_test(
6969
runtime_deps = JUNIT5_RUNTIME_DEPS,
7070
deps = TEST_DEPS,
7171
)
72+
73+
# bazel test //src/test/java/com/williamfiset/algorithms/math:BinomialCoefficientModPrimeTest
74+
java_test(
75+
name = "BinomialCoefficientModPrimeTest",
76+
srcs = ["BinomialCoefficientModPrimeTest.java"],
77+
main_class = "org.junit.platform.console.ConsoleLauncher",
78+
use_testrunner = False,
79+
args = ["--select-class=com.williamfiset.algorithms.math.BinomialCoefficientModPrimeTest"],
80+
runtime_deps = JUNIT5_RUNTIME_DEPS,
81+
deps = TEST_DEPS,
82+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.williamfiset.algorithms.math;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.math.BigInteger;
7+
import org.junit.jupiter.api.*;
8+
9+
public class BinomialCoefficientModPrimeTest {
10+
11+
private static final int MOD = 1_000_000_007;
12+
13+
/** Computes C(n, r) mod p using BigInteger as a reference. */
14+
private static long bigIntCnr(int n, int r, int p) {
15+
BigInteger num = BigInteger.ONE;
16+
BigInteger den = BigInteger.ONE;
17+
for (int i = 0; i < r; i++) {
18+
num = num.multiply(BigInteger.valueOf(n - i));
19+
den = den.multiply(BigInteger.valueOf(i + 1));
20+
}
21+
return num.divide(den).mod(BigInteger.valueOf(p)).longValue();
22+
}
23+
24+
@Test
25+
public void rEqualsZero() {
26+
assertThat(BinomialCoefficientModPrime.compute(10, 0, MOD)).isEqualTo(1);
27+
}
28+
29+
@Test
30+
public void rEqualsN() {
31+
assertThat(BinomialCoefficientModPrime.compute(10, 10, MOD)).isEqualTo(1);
32+
}
33+
34+
@Test
35+
public void smallValues() {
36+
assertThat(BinomialCoefficientModPrime.compute(5, 2, MOD)).isEqualTo(10);
37+
assertThat(BinomialCoefficientModPrime.compute(6, 3, MOD)).isEqualTo(20);
38+
assertThat(BinomialCoefficientModPrime.compute(10, 4, MOD)).isEqualTo(210);
39+
}
40+
41+
@Test
42+
public void knownLargeValue() {
43+
// C(500, 250) mod 10^9+7 = 515561345
44+
assertThat(BinomialCoefficientModPrime.compute(500, 250, MOD)).isEqualTo(515561345L);
45+
}
46+
47+
@Test
48+
public void symmetry() {
49+
// C(n, r) == C(n, n-r)
50+
assertThat(BinomialCoefficientModPrime.compute(100, 30, MOD))
51+
.isEqualTo(BinomialCoefficientModPrime.compute(100, 70, MOD));
52+
}
53+
54+
@Test
55+
public void smallPrime() {
56+
// C(6, 2) = 15, mod 7 = 1
57+
assertThat(BinomialCoefficientModPrime.compute(6, 2, 7)).isEqualTo(1);
58+
}
59+
60+
@Test
61+
public void matchesBigInteger() {
62+
int[] ns = {20, 50, 100, 200, 500};
63+
for (int n : ns) {
64+
for (int r = 0; r <= n; r += Math.max(1, n / 10)) {
65+
long expected = bigIntCnr(n, r, MOD);
66+
assertThat(BinomialCoefficientModPrime.compute(n, r, MOD)).isEqualTo(expected);
67+
}
68+
}
69+
}
70+
71+
@Test
72+
public void negativeNThrows() {
73+
assertThrows(IllegalArgumentException.class,
74+
() -> BinomialCoefficientModPrime.compute(-1, 0, MOD));
75+
}
76+
77+
@Test
78+
public void rGreaterThanNThrows() {
79+
assertThrows(IllegalArgumentException.class,
80+
() -> BinomialCoefficientModPrime.compute(5, 6, MOD));
81+
}
82+
83+
@Test
84+
public void invalidModulusThrows() {
85+
assertThrows(IllegalArgumentException.class,
86+
() -> BinomialCoefficientModPrime.compute(5, 2, 1));
87+
}
88+
}

0 commit comments

Comments
 (0)