From: Mark Day Date: 2007-04-30T03:16:05+09:00 Subject: Re: [QUIZ] Checking Credit Cards (#122) Thanks for another simple but fun quiz! James, thanks for your critiques on last week's quiz; they were educational. I did two versions of the Luhn algorithm. The first one seems more readable to me (though I'm a Ruby nuby), but the second was fun to put together. Comments are welcome. -Mark # # Ruby Quiz #122: Credit card validation # require 'enumerator' class Array def sum(initial=0) inject(initial) { |total, elem| total + elem } end # Compute the pairwise product of two arrays. # That is: result[i] = self[i] * other[i] for i in 0...self.length def pairwise_product(other) result = [] each_index {|i| result << self[i]*other[i] } return result end end class Integer def digits self.to_s.split('').map { |digit| digit.to_i } end end class CreditCard @@types = [["AMEX", /^3[47]\d{13}$/], ["Discover", /^6011\d{12}$/], ["MasterCard", /^5[1-5]\d{14}$/], ["Visa", /^4\d{12}(\d{3})?$/], ["Unknown", //]] attr_reader :type def initialize(str) num = str.delete(" ") # Disallow card "numbers" with non-digits if num =~ /\D/ @type = "Unknown" @valid = false return end # See which of the patterns match the string @type = @@types.find {|name, regexp| num =~ regexp }[0] # See if the card number is valid according to the Luhn algorithm @valid = num.reverse.split('').enum_slice(2).inject(0) do |sum, (odd, even)| sum + odd.to_i + (even.to_i*2).digits.sum end % 10 == 0 =begin # # This works, too. But it seems awfully long and complicated. # # The idea is to combine the digits of the credit card number with # a sequence of 1's and 2's so that every other digit gets doubled. # Then sum up the digits of each product. # # BTW, the "[1,2]*num.length" construct builds an array that's twice # as long as necessary. The entire array only needs num.length # elements, but having more is OK. This was the easy way of making # sure it was big enough. # @valid = num.reverse.to_i.digits.pairwise_product([1,2] *num.length). map{|x| x.digits.sum}.sum % 10 == 0 =end end def valid? @valid end end if __FILE__ == $0 cc = CreditCard.new(ARGV.join) print cc.valid? ? "Valid" : "Invalid", " #{cc.type}\n" end