Skip to content

Commit af72fab

Browse files
vil02Panquesito7realstealthninja
authored
fix: make interface of NCRModuloP fail-safe (TheAlgorithms#2469)
* fix: set proper size of fac * style: use std::size_t as a type of loop counter * style: use uint64_t as a type of loop counter * fix: remove p from the argument list of NCRModuloP::ncr * refactor: add utils namespace * refactor: use references in gcdExtended * refactor: add NCRModuloP::computeFactorialsMod * style: make NCRModuloP::ncr const * test: reorganize tests * test: add missing test cases * refactor: simplify logic * style: make example object const * style: use auto * style: use int64_t to avoid narrowing conversions * docs: update explanation why to import iostream * docs: remove `p` from docstr of `NCRModuloP::ncr` * docs: udpate doc-strs and add example() * Apply suggestions from code review Co-authored-by: David Leal <[email protected]> * dosc: add missing docs * feat: display message when all tests pass Co-authored-by: David Leal <[email protected]> * style: initialize `NCRModuloP::p` with `0` Co-authored-by: David Leal <[email protected]> --------- Co-authored-by: David Leal <[email protected]> Co-authored-by: realstealthninja <[email protected]>
1 parent 8bde3ea commit af72fab

File tree

1 file changed

+128
-83
lines changed

1 file changed

+128
-83
lines changed

math/ncr_modulo_p.cpp

Lines changed: 128 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111

1212
#include <cassert> /// for assert
13-
#include <iostream> /// for io operations
13+
#include <iostream> /// for std::cout
1414
#include <vector> /// for std::vector
1515

1616
/**
@@ -25,71 +25,95 @@ namespace math {
2525
* implementation.
2626
*/
2727
namespace ncr_modulo_p {
28+
2829
/**
29-
* @brief Class which contains all methods required for calculating nCr mod p
30+
* @namespace utils
31+
* @brief this namespace contains the definitions of the functions called from
32+
* the class math::ncr_modulo_p::NCRModuloP
3033
*/
31-
class NCRModuloP {
32-
private:
33-
std::vector<uint64_t> fac{}; /// stores precomputed factorial(i) % p value
34-
uint64_t p = 0; /// the p from (nCr % p)
35-
36-
public:
37-
/** Constructor which precomputes the values of n! % mod from n=0 to size
38-
* and stores them in vector 'fac'
39-
* @params[in] the numbers 'size', 'mod'
40-
*/
41-
NCRModuloP(const uint64_t& size, const uint64_t& mod) {
42-
p = mod;
43-
fac = std::vector<uint64_t>(size);
44-
fac[0] = 1;
45-
for (int i = 1; i <= size; i++) {
46-
fac[i] = (fac[i - 1] * i) % p;
47-
}
34+
namespace utils {
35+
/**
36+
* @brief finds the values x and y such that a*x + b*y = gcd(a,b)
37+
*
38+
* @param[in] a the first input of the gcd
39+
* @param[in] a the second input of the gcd
40+
* @param[out] x the Bézout coefficient of a
41+
* @param[out] y the Bézout coefficient of b
42+
* @return the gcd of a and b
43+
*/
44+
int64_t gcdExtended(const int64_t& a, const int64_t& b, int64_t& x,
45+
int64_t& y) {
46+
if (a == 0) {
47+
x = 0;
48+
y = 1;
49+
return b;
4850
}
4951

50-
/** Finds the value of x, y such that a*x + b*y = gcd(a,b)
51-
*
52-
* @params[in] the numbers 'a', 'b' and address of 'x' and 'y' from above
53-
* equation
54-
* @returns the gcd of a and b
55-
*/
56-
uint64_t gcdExtended(const uint64_t& a, const uint64_t& b, int64_t* x,
57-
int64_t* y) {
58-
if (a == 0) {
59-
*x = 0, *y = 1;
60-
return b;
61-
}
52+
int64_t x1 = 0, y1 = 0;
53+
const int64_t gcd = gcdExtended(b % a, a, x1, y1);
6254

63-
int64_t x1 = 0, y1 = 0;
64-
uint64_t gcd = gcdExtended(b % a, a, &x1, &y1);
55+
x = y1 - (b / a) * x1;
56+
y = x1;
57+
return gcd;
58+
}
6559

66-
*x = y1 - (b / a) * x1;
67-
*y = x1;
68-
return gcd;
60+
/** Find modular inverse of a modulo m i.e. a number x such that (a*x)%m = 1
61+
*
62+
* @param[in] a the number for which the modular inverse is queried
63+
* @param[in] m the modulus
64+
* @return the inverce of a modulo m, if it exists, -1 otherwise
65+
*/
66+
int64_t modInverse(const int64_t& a, const int64_t& m) {
67+
int64_t x = 0, y = 0;
68+
const int64_t g = gcdExtended(a, m, x, y);
69+
if (g != 1) { // modular inverse doesn't exist
70+
return -1;
71+
} else {
72+
return ((x + m) % m);
6973
}
74+
}
75+
} // namespace utils
76+
/**
77+
* @brief Class which contains all methods required for calculating nCr mod p
78+
*/
79+
class NCRModuloP {
80+
private:
81+
const int64_t p = 0; /// the p from (nCr % p)
82+
const std::vector<int64_t>
83+
fac; /// stores precomputed factorial(i) % p value
7084

71-
/** Find modular inverse of a with m i.e. a number x such that (a*x)%m = 1
72-
*
73-
* @params[in] the numbers 'a' and 'm' from above equation
74-
* @returns the modular inverse of a
85+
/**
86+
* @brief computes the array of values of factorials reduced modulo mod
87+
* @param max_arg_val argument of the last factorial stored in the result
88+
* @param mod value of the divisor used to reduce factorials
89+
* @return vector storing factorials of the numbers 0, ..., max_arg_val
90+
* reduced modulo mod
7591
*/
76-
int64_t modInverse(const uint64_t& a, const uint64_t& m) {
77-
int64_t x = 0, y = 0;
78-
uint64_t g = gcdExtended(a, m, &x, &y);
79-
if (g != 1) { // modular inverse doesn't exist
80-
return -1;
81-
} else {
82-
int64_t res = ((x + m) % m);
83-
return res;
92+
static std::vector<int64_t> computeFactorialsMod(const int64_t& max_arg_val,
93+
const int64_t& mod) {
94+
auto res = std::vector<int64_t>(max_arg_val + 1);
95+
res[0] = 1;
96+
for (int64_t i = 1; i <= max_arg_val; i++) {
97+
res[i] = (res[i - 1] * i) % mod;
8498
}
99+
return res;
85100
}
86101

87-
/** Find nCr % p
88-
*
89-
* @params[in] the numbers 'n', 'r' and 'p'
90-
* @returns the value nCr % p
102+
public:
103+
/**
104+
* @brief constructs an NCRModuloP object allowing to compute (nCr)%p for
105+
* inputs from 0 to size
91106
*/
92-
int64_t ncr(const uint64_t& n, const uint64_t& r, const uint64_t& p) {
107+
NCRModuloP(const int64_t& size, const int64_t& p)
108+
: p(p), fac(computeFactorialsMod(size, p)) {}
109+
110+
/**
111+
* @brief computes nCr % p
112+
* @param[in] n the number of objects to be chosen
113+
* @param[in] r the number of objects to choose from
114+
* @return the value nCr % p
115+
*/
116+
int64_t ncr(const int64_t& n, const int64_t& r) const {
93117
// Base cases
94118
if (r > n) {
95119
return 0;
@@ -101,50 +125,71 @@ class NCRModuloP {
101125
return 1;
102126
}
103127
// fac is a global array with fac[r] = (r! % p)
104-
int64_t denominator = modInverse(fac[r], p);
105-
if (denominator < 0) { // modular inverse doesn't exist
106-
return -1;
107-
}
108-
denominator = (denominator * modInverse(fac[n - r], p)) % p;
109-
if (denominator < 0) { // modular inverse doesn't exist
128+
const auto denominator = (fac[r] * fac[n - r]) % p;
129+
const auto denominator_inv = utils::modInverse(denominator, p);
130+
if (denominator_inv < 0) { // modular inverse doesn't exist
110131
return -1;
111132
}
112-
return (fac[n] * denominator) % p;
133+
return (fac[n] * denominator_inv) % p;
113134
}
114135
};
115136
} // namespace ncr_modulo_p
116137
} // namespace math
117138

118139
/**
119-
* @brief Test implementations
120-
* @param ncrObj object which contains the precomputed factorial values and
121-
* ncr function
122-
* @returns void
140+
* @brief tests math::ncr_modulo_p::NCRModuloP
123141
*/
124-
static void tests(math::ncr_modulo_p::NCRModuloP ncrObj) {
125-
// (52323 C 26161) % (1e9 + 7) = 224944353
126-
assert(ncrObj.ncr(52323, 26161, 1000000007) == 224944353);
127-
// 6 C 2 = 30, 30%5 = 0
128-
assert(ncrObj.ncr(6, 2, 5) == 0);
129-
// 7C3 = 35, 35 % 29 = 8
130-
assert(ncrObj.ncr(7, 3, 29) == 6);
142+
static void tests() {
143+
struct TestCase {
144+
const int64_t size;
145+
const int64_t p;
146+
const int64_t n;
147+
const int64_t r;
148+
const int64_t expected;
149+
150+
TestCase(const int64_t size, const int64_t p, const int64_t n,
151+
const int64_t r, const int64_t expected)
152+
: size(size), p(p), n(n), r(r), expected(expected) {}
153+
};
154+
const std::vector<TestCase> test_cases = {
155+
TestCase(60000, 1000000007, 52323, 26161, 224944353),
156+
TestCase(20, 5, 6, 2, 30 % 5),
157+
TestCase(100, 29, 7, 3, 35 % 29),
158+
TestCase(1000, 13, 10, 3, 120 % 13),
159+
TestCase(20, 17, 1, 10, 0),
160+
TestCase(45, 19, 23, 1, 23 % 19),
161+
TestCase(45, 19, 23, 0, 1),
162+
TestCase(45, 19, 23, 23, 1),
163+
TestCase(20, 9, 10, 2, -1)};
164+
for (const auto& tc : test_cases) {
165+
assert(math::ncr_modulo_p::NCRModuloP(tc.size, tc.p).ncr(tc.n, tc.r) ==
166+
tc.expected);
167+
}
168+
169+
std::cout << "\n\nAll tests have successfully passed!\n";
131170
}
132171

133172
/**
134-
* @brief Main function
135-
* @returns 0 on exit
173+
* @brief example showing the usage of the math::ncr_modulo_p::NCRModuloP class
136174
*/
137-
int main() {
138-
// populate the fac array
139-
const uint64_t size = 1e6 + 1;
140-
const uint64_t p = 1e9 + 7;
141-
math::ncr_modulo_p::NCRModuloP ncrObj =
142-
math::ncr_modulo_p::NCRModuloP(size, p);
143-
// test 6Ci for i=0 to 7
175+
void example() {
176+
const int64_t size = 1e6 + 1;
177+
const int64_t p = 1e9 + 7;
178+
179+
// the ncrObj contains the precomputed values of factorials modulo p for
180+
// values from 0 to size
181+
const auto ncrObj = math::ncr_modulo_p::NCRModuloP(size, p);
182+
183+
// having the ncrObj we can efficiently query the values of (n C r)%p
184+
// note that time of the computation does not depend on size
144185
for (int i = 0; i <= 7; i++) {
145-
std::cout << 6 << "C" << i << " = " << ncrObj.ncr(6, i, p) << "\n";
186+
std::cout << 6 << "C" << i << " mod " << p << " = " << ncrObj.ncr(6, i)
187+
<< "\n";
146188
}
147-
tests(ncrObj); // execute the tests
148-
std::cout << "Assertions passed\n";
189+
}
190+
191+
int main() {
192+
tests();
193+
example();
149194
return 0;
150195
}

0 commit comments

Comments
 (0)