Skip to content

Instantly share code, notes, and snippets.

@mashingan
Last active August 29, 2015 14:13
Show Gist options
  • Save mashingan/d317ea7dfc831bb138d0 to your computer and use it in GitHub Desktop.
Save mashingan/d317ea7dfc831bb138d0 to your computer and use it in GitHub Desktop.
/*
* The call in console:
* $ node digitcombinatorial.js [maxnumber] [digitpattern]
*
* If maxnumber and digitpattern is not supplied in command line, it will
* fallback to the default 10000 and 14 respectively
*/
// Initial parameter definition. Since it is parameter, it's expected won't
// be changed during runtime.
var assert = require('assert');
var MAXNUM,
DIGITS;
if(process.argv[2]) {
MAXNUM = (parseInt(process.argv[2])-1).toString().length;
} else {
MAXNUM = (10000-1).toString().length;
}
if(process.argv[3]) {
DIGITS = process.argv[3].length;
} else {
DIGITS = '14'.length;
}
// Combinatorial mechanics which consist of factorial, combination,
// and case-specific combinatorial e.g. Cn and Ck.
// The use of function instead of object because of immutability input
// and always of return the result as using object requires immutable
// and restricts the generality functions cascading.
function factorial(n, start) {
start = start || 1;
var sum = 1;
if(n === 0) return 1;
for(var i=start+1; i<=n; i++)
sum *= i;
return sum;
}
function permutation(n, r, label) {
label = label || 'repeat';
denom = (label === 'repeat') ? 1 : (n - r);
return (label === 'repeat') ? Math.pow(n, r) : factorial(n, r);
}
function combination(n, r, label) {
label = label || 'repeat';
var num, denom;
if(label === 'repeat') {
num = r - 1;
denom = 1;
} else {
num = 0;
denom = r;
}
return factorial(n+num) / (factorial(r) * factorial(n-denom));
}
function Cn(n, r) {
return combination(n, r) * Math.pow(10, r);
}
/*
* From recursively combination, we got
* result = Ck(n, r) = Cn(n, r) - (Ck(n+1, r-digits)) while r >= 0
* where
* Cn = C(n, r) . 10^r; and
* digits = total length of pattern (e.g. 14 == 2, 115 == 3 etc)
* <==>
* if digits == 2
* result = Cn(n, r) - (Cn(n+1, r-2) - (Cn(n+2, r-4) - (Cn(n+3, r-6) - ...)))
* <==>
* result = Cn(n, r) - Cn(n+1, r-2) + Cn(n+2, r-4) - Cn(n+3, r-6) + ....
* <==>
* result = Cn0(n, r-digits) - (-1)^(i-1) * Cni(n+1, r-(digits*i))
*/
function Ck(n, r){
var sum = Cn(n, r);
for(var i=1, iter = r - DIGITS; iter >= 0; i++, iter -= DIGITS) {
sum -= (Math.pow(-1, i-1) * Cn(n+i, iter));
}
return sum;
}
/*
* testmaker(Object ParamTest, failinput1, failinput2, ...)
* Function which return testing function template based on
* ParamTest object.
* This is simple test framework which specifically targeted for positive
* integer hence using assert.strictEqual.
* The inputs for supplying failing test callback are using variable
* arguments, (e.g. failinput1, failinput2 ....)
*/
function testmaker(param) {
var failinputs = [];
for(var i=1; i<arguments.length; i++)
failinputs.push(arguments[i]);
if(param.values.length !== param.expected.length) {
throw new Error('Supplied values and expected are not same');
}
return function() {
var success = 0;
var totaltest = param.values.length + failinputs.length;
function equaltest(value, expected) {
try {
assert.strictEqual(value, expected);
success++;
} catch (e) {
console.error(e);
}
}
function failtest(inputs) {
var catching = false;
try {
if(inputs instanceof Array){
assert.fail(param.callback.apply(inputs), 'error',
'Invalid input', param.operator);
} else {
assert.fail(param.callback.call(inputs), 'error',
'Invalid input', param.operator);
}
} catch (e) {
catching = !catching;
} finally {
if(catching) {
console.log('Catching success');
success++;
} else {
console.log('Catching failed');
}
}
}
for(var i=0; i<param.values.length; i++) {
var value = param.values[i];
var expected = param.expected[i];
equaltest(value, expected);
}
for(var i=0; i<failinputs.length; i++) {
var input = failinputs[i];
failtest(input);
}
if(success === totaltest) {
console.log('All ' + param.label + ' tests success');
} else {
console.error('Failed: ' + (totaltest-success) + ' from ' +
totaltest + ' tests');
}
console.log('----------');
};
}
/*
* ParamTest object
* The datatype (or struct or record etc) for supplying the testmaker.
* Note that this object only acts as a data packs and not a mutable one
* hence the lack of methods in the definition.
*/
function ParamTest(label, values, expected, callback) {
this.label = label;
this.values = values;
this.expected = expected;
this.callback = callback;
}
var tests = [
testmaker(new ParamTest(
'factorial',
[factorial(5), factorial(0), factorial(5, 4), factorial(5, 3)],
[120, 1, 5, 20],
factorial),
'a', -1),
testmaker(new ParamTest(
'Ck',
[Ck(2, 0), Ck(2, 1), Ck(2, 2)],
[1, 20, 299],
Ck),
[2, -1], 2, ['a', 2])
];
for(var i=0; i<tests.length; i++){
tests[i]();
}
var inputs = [10000, 10000000, 10000000000]
console.log('\n----------');
console.log('Iteration from command line');
console.log('Iteration: C(' +2+ ', ' +(MAXNUM-DIGITS) + ')');
console.log(Ck(2, MAXNUM-DIGITS));
console.log();
console.log('Iteration with predetermined inputs');
for(var i=0; i<inputs.length; i++) {
console.log('----------');
var input = inputs[i];
var supply = (input-1).toString().length - 2;
console.log('Input ' + input + ': ' + Ck(2, supply));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment