From: Sebastian Hungerecker on 29 Apr 2007 08:37 Ruby Quiz wrote: > This week's Ruby Quiz is to write a program that accepts a credit card > number as a command-line argument. The program should print the card's > type (or Unknown) as well a Valid/Invalid indication of whether or not the > card passes the Luhn algorithm. I'm rather new to ruby and this is the first time I participated in this quiz. If found this one to be rather easy and fun to work with. Here's my solution: #!/usr/bin/env ruby class CreditCard attr_reader :number CardTypes = [ { :name => "AMEX", :regex => /(34|37)\d{13}/, :luhn => true}, { :name => "Bankcard", :regex => /5610\d{12}/, :luhn => true}, { :name => "Bankcard", :regex => /56022[1-5]\d{10}/, :luhn => true}, { :name => "China Union Pay", :regex => /622\d{13}/, :luhn => false}, { :name => "DC-CB", :regex => /30[0-5]\d{11}/, :luhn => true}, { :name => "DC-eR", :regex => /2(014|149)\d{11}/, :luhn => false}, { :name => "DC-Int", :regex => /36\d{12}/, :luhn => true}, { :name => "DC-UC or MasterCard", :regex => /55\d{14}/, :luhn => true}, { :name => "Discover", :regex => /6011\d{12}/, :luhn => true}, { :name => "MasterCard", :regex => /5[1-4]\d{14}/, :luhn => true}, { :name =>"Maestro", :regex => /(5020|5038|6759)\d{12}/, :luhn => true}, { :name => "Visa", :regex => /4(\d{13}|\d{16})/, :luhn => true}, { :name => "Unknown", :regex => //, :luhn => true} ] # If the credit card is of unknown type, we'll just assume # that it can be verified using the Luhn algorithm. def initialize(num) self.number=num end def number=(num) raise ArgumentError, "Supplied argument is not a number" unless num.to_s =~ /^[-_\s\d]+$/ @number=num.to_s.gsub(/(\s|_|-)/,'') @type=nil @validity=nil end def card_type @type||=CardTypes.detect {|i| i[:regex].match @number} end def to_s "Number: #{@number}, Type: #{card_type[:name]}, Valid: #{valid?}" end def valid? return @validity unless @validity.nil? return @validity="unknown" unless card_type[:luhn] arr=(a)number.split(//).reverse.map {|x| x.to_i} arr.each_with_index{|v,i| arr[i]=v*2 if i%2==1} sum=arr.join.split(//).map do |x| x.to_i end.inject {|s,i| i+s} @validity = sum%10==0 end end if __FILE__==$0 card=CreditCard.new(if ARGV.empty? puts "Please enter your credit card number:" gets.chomp else ARGV.join end) puts card end
From: Robert Dober on 29 Apr 2007 08:47 Yet another Great Ruby Quiz ;) Well I thought this was a Quiz particularly suited to write *nice* code. I guess I somehow failed as it is too long, but I put some of the features I like most of Ruby, well I guess it is a solution which is consistent with my style ;) And as I feel that this is not said frequently enough, I'll just say it: "Thank you James for all these Quizzes, and thanks to the Quiz creators too of course." Cheers Robert #!/usr/bin/ruby # vim: sts=2 sw=2 expandtab nu tw=0: class String def to_rgx Regexp.new self end def ccc Checker.new{ amex [34,37], 15 discover 6011, 16 master 50..55, 16 visa 4, [13,16] jcb 3528..3589, 16 }.check self end end class Checker UsageException = Class.new Exception def initialize &blk @cards = {} instance_eval &blk end def check str s = str.gsub(/\s/,"") @cards.each do |card, check_values| return [ luhn( s ), card.to_s.capitalize ] if check_values.first === s && check_values.last.include?( s.length ) end [ nil, "Unknown" ] end def luhn s sum = 0 s.split(//).reverse.each_with_index{ | digit, idx | sum += (idx%2).succ * digit.to_i } (sum % 10).zero? ? " Valid" : "n Invalid" end # This is one of the rare examples where # the method_missing parametes are not # id, *args, &blk, trust me I know what # I am doing ;) def method_missing credit_card_name, regs, lens raise UsageException, "#{card_name} defined twice" if @cards[credit_card_name] ### Unifying Integer, Array and Range parameters lens = [lens] if Integer === lens lens = lens.to_a ### Genereating regular expressions regs = [regs] if Integer === regs regs = regs.map{ |r| "^#{r}" }.join("|").to_rgx @cards[credit_card_name] = [ regs, lens ] end end ARGV.each do | number | puts "Card with number #{number} is a%s %s card" % number.ccc end # ARGV.each do
From: Gordon Thiesfeld on 29 Apr 2007 09:15 Here's my solution. require 'enumerator' class CardProcessor CARDS = {'visa' => {:length => [13,16], :begin => [4]}, 'amex' => {:length => [15], :begin => [34,37]}, 'discover' => {:length => [16], :begin => [6011]}, 'mastercard' => {:length => [16], :begin => (51..55)}, 'jcb' => {:length => [16], :begin => (3528..3589)}, 'diners club' => {:length => [14], :begin => [(3000..3029).to_a, (3040..3059).to_a, 36, (3815..3889).to_a, 389].flatten} } def initialize(name, number) @name = name.downcase @number = number.gsub(/\D/,'') end def luhn_valid? a = '' @number.split('').reverse.each_slice(2){ |leave, double| a << leave << (double.to_i * 2).to_s } a.split('').inject(0){|s,v| s + v.to_i } % 10 == 0 end def length_valid? CARDS[@name][:length].include? @number.size end def beginning_valid? @number =~ /^#{CARDS[@name][:begin].to_a.join('|')}/ end def valid? beginning_valid? && length_valid? && luhn_valid? end def self.cards CARDS.keys end end if __FILE__ == $0 if ARGV.empty? puts "Usage ruby #{File.basename($0)} <cardnumber>" exit 0 end number = ARGV.join if CardProcessor.new('', number).luhn_valid? puts "Your card appears to be a valid card." result = CardProcessor.cards.map {|card| card if CardProcessor.new(card, number).valid? }.compact puts "Vendor: #{(result.empty? ? 'unknown' : result.first).capitalize}" else puts "Your card doesn't appear to be valid." end end
From: Ari Brown on 29 Apr 2007 09:24 On Apr 29, 2007, at 1:14 AM, Mark Day wrote: <snip> > This confused me at first, too. Let's take just the last four > digits: 7893. Counting from the end, double every second digit, > leaving the others unchanged. That gives you: 14, 8, 18, 3 (where > 14=7*2 and 18=9*2; the 8 and the 3 are unchanged). Now add up every > digit of the resulting sequence: 1+4 + 8 + 1+8 + 3. Note the > "1+4" comes from the 14, and the "1+8" comes from the 18. Tons, your and philip's responses have definitely cleared this up. Now I can begin writing it. --------------------------------------------| If you're not living on the edge, then you're just wasting space.
From: Ryan Leavengood on 29 Apr 2007 10:42
Here is my solution, including unit tests. I'm pretty pleased with my luhn check. require 'enumerator' class CardType < Struct.new(:name, :pattern, :lengths) def match(cc) (cc =~ pattern) and lengths.include?(cc.length) end def to_s name end end class CardValidator @types = [ CardType.new('AMEX', /^(34|37)/, [15]), CardType.new('Discover', /^6011/, [16]), CardType.new('MasterCard', /^5[1-5]/, [16]), CardType.new('Visa', /^4/, [13,16]) ] def self.card_type(cc) @types.find {|type| type.match(cc) } end def self.luhn_check(cc) # I like functional-style code (though this may be a bit over the top) (cc.split('').reverse.enum_for(:each_slice, 2).inject('') do |s, (a, b)| s << a + (b.to_i * 2).to_s end.split('').inject(0) {|sum, n| sum + n.to_i}) % 10 == 0 end end require 'test/unit' class CardValidatorTest < Test::Unit::TestCase def test_card_type assert_equal('AMEX', CardValidator.card_type('341122567979797').name) assert_equal('AMEX', CardValidator.card_type('371122567979797').name) assert_equal('Discover', CardValidator.card_type('6011123456781122').name) assert_equal('MasterCard', CardValidator.card_type('5115666677779999').name) assert_equal('MasterCard', CardValidator.card_type('5315666677779999').name) assert_equal('Visa', CardValidator.card_type('4408041234567893').name) assert_equal('Visa', CardValidator.card_type('4417123456789112').name) assert_equal('Visa', CardValidator.card_type('4417123456789').name) assert_nil(CardValidator.card_type('3411225679797973')) assert_nil(CardValidator.card_type('601112345678112')) assert_nil(CardValidator.card_type('51156666777799')) assert_nil(CardValidator.card_type('5615666677779989')) assert_nil(CardValidator.card_type('1111222233334444')) assert_nil(CardValidator.card_type('44171234567898')) end def test_luhn_check assert(CardValidator.luhn_check('1111222233334444')) assert(CardValidator.luhn_check('4408041234567893')) assert(!CardValidator.luhn_check('4417123456789112')) assert(!CardValidator.luhn_check('6011484800032882')) end end if $0 == __FILE__ abort("Usage: #$0 <credit card number> or -t to run unit tests") if ARGV.length < 1 if not ARGV.delete('-t') Test::Unit.run = true cc = ARGV.join.gsub(/\s*/, '') type = CardValidator.card_type(cc) puts "Card type is: #{type ? type : 'Unknown'}" puts "The card is #{CardValidator.luhn_check(cc) ? 'Valid' : 'Invalid'}" end end |