Skip to content

Instantly share code, notes, and snippets.

@jaredcacurak
Last active September 28, 2015 10:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jaredcacurak/1426203 to your computer and use it in GitHub Desktop.
Save jaredcacurak/1426203 to your computer and use it in GitHub Desktop.
Coding Challenge: The Luhny Bin http://corner.squareup.com/2011/11/luhny-bin.html
function LuhnyBin(creditCard, minLength, maxLength, maskWithChar) {
if (this instanceof LuhnyBin) {
this.creditCard = creditCard;
this.minLength = minLength || 14;
this.maxLength = maxLength || 16;
this.maskWithChar = maskWithChar || 'X';
this.output = this.process(creditCard);
} else {
return new LuhnyBin(creditCard, minLength, maxLength, maskWithChar);
}
}
LuhnyBin.prototype.process = function (creditCard) {
var scrubbedCreditCard = this.scrub(creditCard);
return this.isLuhn(scrubbedCreditCard)
? this.mask(creditCard)
: creditCard;
};
LuhnyBin.prototype.scrub = function (s) {
return s.replace(/\D/g, '')
.split('')
.map(function (element) {
return +element;
});
};
LuhnyBin.prototype.isLuhn = function (a) {
var length = a.length;
return length >= this.minLength
&& length <= this.maxLength
&& this.luhnSum(a) % 10 === 0;
};
LuhnyBin.prototype.luhnSum = function (numbers) {
var that, parity;
that = this;
parity = numbers.length % 2;
return numbers.reduceRight(function (previousValue, currentValue, index) {
var value = (index % 2 === parity)
? that.luhns[currentValue]
: currentValue;
return previousValue + value;
});
};
LuhnyBin.prototype.luhns = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9];
LuhnyBin.prototype.mask = function (s) {
return s.replace(/\d/g, this.maskWithChar);
};
@briancavalier
Copy link

Looks like it works! I threw it into jsfiddle just to try it out and used the simple 4111... number from the blog post. Overall, I like the abstractions and the code is really clean, and reads well--I actually had no trouble understanding it even before I read the puzzle!

I see a few things you may want to think about:

  1. The way it's structured right now, each time the maskCreditCardNumber function is called, it will have to create the 19 nested functions, which could be a bit hit if maskCreditCardNumber is used in a tight loop. There are certainly times where nested functions are the way to go (i.e. when they need variables in the closure context), but in this case, I think I'd hoist as many of the utility functions out of there as possible.
  2. There are quite a few trips through the string/array. Every filter/map/reduce/reverse call is a full trip through the input. Granted it's only 14-16 chars, but you may actually be closer to O(n^2) here than O(n). Again, in a tight loop the difference would probably be measurable. It'd be interesting to see if you can get it closer to O(n) ... for example, calculateLuhn reverses the array(trip 1), and then does 3 reduce operations (1 in sum and 2 in sumOf) and 2 filters, and 1 map! That's already 7 trips. You may be able to do all of that in a single, reverse for loop.
  3. If you end up still using reduce (as long as you assume an ES5-compliant Javascript VM, like node, or recent WebKit/FF), you can use array.reduce instead of rolling your own.
  4. I might simplify toArrayOfIndividualDigits, or maybe identityOf. Something like number >>> 0; or even parseInt(number, 10); will probably be a lot faster than the multiply() function call. Also for scrub(), I might just do return value.replace(/\D/g, '');

Good job ... pretty interesting puzzle, too :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment