I posted a similar question “Curious Numbers” (HackerRank PE 34) recently (but in a different language/platform altogether). I'm starting to learn JavaScript and decided to take the challenge again and adapt it to this language's features and quirks.
While I could have written many of the functions below using for-loops, I decided instead to focus on getting better with the FP aspects of JS, in particular map
, reduce
, filter
and apply
.
I think it is pretty fast, with the longest of the 5 challenges on HackerRank executing in 2.28s for a calculation in the neighborhood of \$10^5\$, but I'm certainly open to ways to make it faster, cleaner, or better in any other way.
\$19!\$ is a curious number, as \$1!+9!=1+362880=362881\$ is divisible by \$19\$.
Find the sum of all numbers below \$N\$ which divide the sum of the factorial of their digits. Note: as \$1!,2!,\cdots,9!\$ are not sums, so they are not included.
Input Format: Input contains an integer \$N\$
Output Format: Print the answer corresponding to the test case.
Constraints: \$10^1 \le N \le 10^5\$
Sample Input
20
Sample Output
19
// HackerRank Project Euler #34: Digit factorials
// https://www.hackerrank.com/contests/projecteuler/challenges/euler034
function isStrictInt(input) {
// Confirms whether input is 1) of number type, 2) not equal to the NaN constant, and 3) can be parsed to an integer.
// Reference: http://stackoverflow.com/a/29658971/3626537
return typeof input === "number"
&& !isNaN(input)
&& parseInt(input) === input;
}
function arrayOfNCopies(value, N) {
// Makes an array of N copies of value.
if (!isStrictInt(N)) {
return NaN;
}
else if (N === 0) {
return [];
}
return Array(Math.abs(N) + 1).join(value).split("");
}
function arrayOfNConsecutiveInts(N) {
// Makes an array of consecutive integers from 1 to N.
// Ex: arrayOfNConsecutiveInts(5) => [1,2,3,4,5]
if (!isStrictInt(N)) {
return NaN;
}
else if (N === 0) {
return [];
}
return Array
// makes array of N size and assigns `undefined` to each index (to avoid nulls)
.apply(null, Array(N))
.map(function (a, b) { return b + 1; });
}
function powerOf(base, exponent) {
// Calculates an exponential (i.e. B^X or "B to the power of X").
// Ex: powerOf(2,3) => 2 * 2 * 2 => 8
if (!isStrictInt(base) || !isStrictInt(exponent)) {
return NaN;
}
else if (exponent === 0) {
return 1;
}
else if (base === 0) {
return 0;
}
else {
var result = arrayOfNCopies(base, exponent).reduce(function(a, b) { return a * b; });
if (exponent < 0) {
return 1 / result;
}
return result;
}
}
function factorial(N) {
// Calculates N! (i.e. N factorial).
// Ex: factorial(4) => 4 * 3 * 2 * 1 => 24
if (!isStrictInt(N)) {
return NaN;
}
// Negative numbers' factorials are mathematically undefined:
else if (N < 0) {
return NaN;
}
else if (N === 0) {
return 1;
}
return arrayOfNConsecutiveInts(N).reduce(function (a, b) {
return a * b;
});
}
function explodeIntToDigits(N) {
// Given a number N, decompose it into an array of its digits.
// Ex: explodeIntToDigits(1234) => [1, 2, 3, 4]
if (!isStrictInt(N)) {
return NaN;
}
return N.toString(10).split("").map(function (t) {
return parseInt(t);
});
}
function sumOfFactorialOfDigits(N) {
// Given a number N, returns the sum of the factorials of each digit of N.
// Ex: sumOfFactorialOfDigits(35) => 3! + 5! => 6 + 120 => 126
if (!isStrictInt(N)) {
return NaN;
}
return explodeIntToDigits(N).map(function (t) {
return factorial(t);
}).reduce(function (a, b) {
return a + b;
});
}
function isCuriousNumber(N) {
// A 'Curious Number' is a number where the sum of the factorial of each of its digits is evenly divisible by the number itself.
// Ex: isCuriousNumber(19) => true, because: 1! + 9! = 1 + 362880 = 362881, and: 362881 % 19 = 0
if (!isStrictInt(N)) {
return NaN;
}
else if (sumOfFactorialOfDigits(N) % N !== 0) {
return false;
}
return true;
}
function sumAllCuriousNumbersUpTo(N) {
// Given a number N up to 10^5, return the sum of a list of all 'Curious Numbers' 10 to N inclusive.
// This is as per constraint: 10 ≤ N ≤ 10^5
if (!isStrictInt(N)) {
return NaN;
}
return arrayOfNConsecutiveInts(N).filter(function (t) {
return t >= 10 && isCuriousNumber(t);
}).reduce(function (a, b) {
return a + b;
}, 0);
}