diff --git a/DIRECTORY.md b/DIRECTORY.md index b7b8aca45d..d128f91650 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -249,6 +249,7 @@ * [PrimeFactors](Maths/PrimeFactors.js) * [QuadraticRoots](Maths/QuadraticRoots.js) * [RadianToDegree](Maths/RadianToDegree.js) + * [RepunitTheorem](Maths/RepunitTheorem.js) * [ReverseNumber](Maths/ReverseNumber.js) * [ReversePolishNotation](Maths/ReversePolishNotation.js) * [RowEchelon](Maths/RowEchelon.js) @@ -322,8 +323,8 @@ * [TernarySearch](Search/TernarySearch.js) * [UnionFind](Search/UnionFind.js) * **Sliding-Windows** - * [MaxSumSubarrayFixed](Sliding-Windows/MaxSumSubarrayFixed.js) * [LongestSubarrayWithSumAtMost](Sliding-Windows/LongestSubarrayWithSumAtMost.js) + * [MaxSumSubarrayFixed](Sliding-Windows/MaxSumSubarrayFixed.js) * **Sorts** * [AlphaNumericalSort](Sorts/AlphaNumericalSort.js) * [BeadSort](Sorts/BeadSort.js) diff --git a/Maths/RepunitTheorem.js b/Maths/RepunitTheorem.js new file mode 100644 index 0000000000..23c1e5d279 --- /dev/null +++ b/Maths/RepunitTheorem.js @@ -0,0 +1,79 @@ +/** + * Repunit theorem helpers. + * + * A repunit of length n is: + * R_n = (10^n - 1) / 9 + * + * For a prime p (p != 2, 3, 5), p divides R_n iff ord_p(10) divides n. + * Reference: https://en.wikipedia.org/wiki/Repunit + */ + +const gcd = (a, b) => { + let x = BigInt(a) + let y = BigInt(b) + while (y !== 0n) { + ;[x, y] = [y, x % y] + } + return x < 0n ? -x : x +} + +const modPow = (base, exp, mod) => { + let result = 1n + let b = BigInt(base) % BigInt(mod) + let e = BigInt(exp) + const m = BigInt(mod) + + while (e > 0n) { + if (e & 1n) result = (result * b) % m + b = (b * b) % m + e >>= 1n + } + + return result +} + +const multiplicativeOrder10 = (prime) => { + const p = BigInt(prime) + if (p <= 1n) throw new RangeError('prime must be > 1') + if (gcd(10n, p) !== 1n) throw new RangeError('10 and prime must be coprime') + + // For prime p, ord_p(10) divides p-1. + const upper = p - 1n + for (let k = 1n; k <= upper; k++) { + if (upper % k === 0n && modPow(10n, k, p) === 1n) { + return k + } + } + + throw new Error('multiplicative order not found') +} + +const repunitMod = (length, mod) => { + if (!Number.isInteger(length) || length < 1) { + throw new RangeError('length must be a positive integer') + } + const m = BigInt(mod) + if (m <= 0n) throw new RangeError('mod must be > 0') + + let remainder = 0n + for (let i = 0; i < length; i++) { + remainder = (remainder * 10n + 1n) % m + } + return remainder +} + +const isRepunitDivisibleByPrime = (length, prime) => { + if (!Number.isInteger(length) || length < 1) { + throw new RangeError('length must be a positive integer') + } + + const p = BigInt(prime) + if (p === 2n || p === 5n) return false + if (p === 3n) return BigInt(length) % 3n === 0n + if (gcd(10n, p) !== 1n) return false + + const order = multiplicativeOrder10(p) + return BigInt(length) % order === 0n +} + +export { multiplicativeOrder10, repunitMod, isRepunitDivisibleByPrime } diff --git a/Maths/test/RepunitTheorem.test.js b/Maths/test/RepunitTheorem.test.js new file mode 100644 index 0000000000..ce886d3507 --- /dev/null +++ b/Maths/test/RepunitTheorem.test.js @@ -0,0 +1,50 @@ +import { + isRepunitDivisibleByPrime, + multiplicativeOrder10, + repunitMod +} from '../RepunitTheorem' + +describe('RepunitTheorem', () => { + it('computes multiplicative order examples', () => { + expect(multiplicativeOrder10(11n)).toBe(2n) + expect(multiplicativeOrder10(37n)).toBe(3n) + expect(multiplicativeOrder10(7n)).toBe(6n) + }) + + it('checks repunit divisibility using the theorem', () => { + // 111111 is divisible by 3, 7, 11, 13, 37 + expect(isRepunitDivisibleByPrime(6, 3n)).toBe(true) + expect(isRepunitDivisibleByPrime(6, 7n)).toBe(true) + expect(isRepunitDivisibleByPrime(6, 11n)).toBe(true) + expect(isRepunitDivisibleByPrime(6, 13n)).toBe(true) + expect(isRepunitDivisibleByPrime(6, 37n)).toBe(true) + }) + + it('returns false when divisibility condition does not hold', () => { + expect(isRepunitDivisibleByPrime(1, 3n)).toBe(false) + expect(isRepunitDivisibleByPrime(3, 3n)).toBe(true) + expect(isRepunitDivisibleByPrime(6, 19n)).toBe(false) + expect(isRepunitDivisibleByPrime(9, 2n)).toBe(false) + expect(isRepunitDivisibleByPrime(9, 5n)).toBe(false) + }) + + it('computes repunit modulo without building huge integers', () => { + expect(repunitMod(6, 37n)).toBe(0n) + expect(repunitMod(6, 11n)).toBe(0n) + expect(repunitMod(7, 13n)).toBe(1n) + }) + + it('validates multiplicative order input', () => { + expect(() => multiplicativeOrder10(1n)).toThrow(RangeError) + expect(() => multiplicativeOrder10(10n)).toThrow(RangeError) + }) + + it('validates repunitMod input', () => { + expect(() => repunitMod(0, 7n)).toThrow(RangeError) + expect(() => repunitMod(5, 0n)).toThrow(RangeError) + }) + + it('validates repunit divisibility input length', () => { + expect(() => isRepunitDivisibleByPrime(0, 7n)).toThrow(RangeError) + }) +})