[QUIZ] Checking Credit Cards (#122)

Here is my solution a bit schoolboyish :frowning:

BEGIN : quiz122.rb

···

-------------------------------------------------------------------------------
#!/usr/bin/ruby

def is_luhn? cardno

  digits = cardno.split(//).reverse!

  sum = ''
  digits.each_index do |i|
    if i%2 == 0
      sum << digits[i]
    else
      sum << (2 * digits[i].to_i).to_s
    end

  end
  s1 = 0
  sum.split(//).inject(s1) { |s1, v| s1 += v.to_i }
  if s1 % 10 == 0
    return "Valid"
  else
    return "InValid"
  end

end

if __FILE__ == $0:

  abort("Usage: ruby quiz122.rb #CARDNO") if ARGV.length != 1

  cardno = ARGV[0].gsub(/[^0-9]/, '')

  is_luhn? cardno

  print "card number \"#{cardno}\" is "
  case cardno.length
  when 13
    if cardno.match(/^4/)
      print "Visa : "
    else
      print "Unknown : "
    end
    puts is_luhn?(cardno)

  when 14
    if cardno.match(/(^30[0-2][0-9])|(^30[4-5][0-9])|(^36)|(^38(1[5-9]|[2-9]))/)
      print "Diners : "
    else
      print "Unknown : "
    end

  when 15
    if cardno.match(/^3(4|7)/)
      print "Amex : "
    else
      print "Unknown : "
    end

  when 16
    if cardno.match(/^6011/)
      print "Discover : "
    elsif cardno.match(/^4/)
      print "Visa : "
    elsif cardno.match(/^5[1-5]/)
      print "MasterCard : "
    elsif cardno.match(/^35(2[8-9]|[3-8][0-9])/)
      print "JCB : "
    else
      print "Unknown : "
    end

  else
    print "Unknown : "

  end

  puts is_luhn?(cardno) + " CC "

end
------------------------------------------------------------------------------

On 4/27/07, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Before a credit card is submitted to a financial institution, it generally makes
sense to run some simple reality checks on the number. The numbers are a good
length and it's common to make minor transcription errors when the card is not
scanned directly.

The first check people often do is to validate that the card matches a known
pattern from one of the accepted card providers. Some of these patterns are:

        +============+=============+===============+
        > Card Type | Begins With | Number Length |
        +============+=============+===============+
        > AMEX | 34 or 37 | 15 |
        +------------+-------------+---------------+
        > Discover | 6011 | 16 |
        +------------+-------------+---------------+
        > MasterCard | 51-55 | 16 |
        +------------+-------------+---------------+
        > Visa | 4 | 13 or 16 |
        +------------+-------------+---------------+

All of these card types also generate numbers such that they can be validated by
the Luhn algorithm, so that's the second check systems usually try. The steps
are:

        1. Starting with the next to last digit and continuing with every other
           digit going back to the beginning of the card, double the digit
        2. Sum all doubled and untouched digits in the number
        3. If that total is a multiple of 10, the number is valid

For example, given the card number 4408 0412 3456 7893:

        Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
        Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
        Step 3: 70 % 10 == 0

Thus that card is valid.

Let's try one more, 4417 1234 5678 9112:

        Step 1: 8 4 2 7 2 2 6 4 10 6 14 8 18 1 2 2
        Step 2: 8+4+2+7+2+2+6+4+1+0+6+1+4+8+1+8+1+2+2 = 69
        Step 3: 69 % 10 != 0

That card is not valid.

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.

--
अभिजीत

[ written in http://www.paahijen.com/scratchpad ]

[ http://www.paahijen.com ]

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

···

On Apr 27, 6:59 pm, Ruby Quiz <j...@grayproductions.net> wrote:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=-=-=

Before a credit card is submitted to a financial institution, it generally makes
sense to run some simple reality checks on the number. The numbers are a good
length and it's common to make minor transcription errors when the card is not
scanned directly.

The first check people often do is to validate that the card matches a known
pattern from one of the accepted card providers. Some of these patterns are:

        +============+=============+===============+
        > Card Type | Begins With | Number Length |
        +============+=============+===============+
        > AMEX | 34 or 37 | 15 |
        +------------+-------------+---------------+
        > Discover | 6011 | 16 |
        +------------+-------------+---------------+
        > MasterCard | 51-55 | 16 |
        +------------+-------------+---------------+
        > Visa | 4 | 13 or 16 |
        +------------+-------------+---------------+

All of these card types also generate numbers such that they can be validated by
the Luhn algorithm, so that's the second check systems usually try. The steps
are:

        1. Starting with the next to last digit and continuing with every other
           digit going back to the beginning of the card, double the digit
        2. Sum all doubled and untouched digits in the number
        3. If that total is a multiple of 10, the number is valid

For example, given the card number 4408 0412 3456 7893:

        Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
        Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
        Step 3: 70 % 10 == 0

Thus that card is valid.

Let's try one more, 4417 1234 5678 9112:

        Step 1: 8 4 2 7 2 2 6 4 10 6 14 8 18 1 2 2
        Step 2: 8+4+2+7+2+2+6+4+1+0+6+1+4+8+1+8+1+2+2 = 69
        Step 3: 69 % 10 != 0

That card is not valid.

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.

Here is my solution.
#!/usr/bin/ruby

credit_card_number = ARGV.join

case
when (credit_card_number=~/^(34|37)\d{13}$/): print 'AMEX '
when (credit_card_number=~/^6011\d{12}$/): print 'Discover '
when (credit_card_number=~/^5[1-5]\d{14}$/): print 'MasterCard '
when (credit_card_number=~/^4(\d{12}|\d{15})$/): print 'Visa '
else print 'Unknown '
end

i = 0
luhl_number = ''
credit_card_number.reverse.each_byte {|char|
  if (i%2==1) then
    char = (char.chr.to_i * 2).to_s
  else
    char = char.chr
  end
  luhl_number = char + luhl_number
  i += 1
}

sum_total = 0

luhl_number.each_byte {|char|
  sum_total += char.chr.to_i
}

if (sum_total%10==0) then
  print "Valid\n"
else
  print "Invalid\n"
end

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.

This is just a class, with a bit of starter code at the bottom to get going.
It will take any number of CC numbers on the command line, but they must be
quoted. The luhn method seems tacky, but seems to work.

···

#################################
class CCNumberError < StandardError
end

class CardValidate
  attr_reader :cc_number, :cc_type, :luhn_valid
  def initialize(cc_number)
    @cc_number = cc_number
    normalise_cc_number
    card_type
    luhn
  end

  private
  def normalise_cc_number
    @cc_number = @cc_number.gsub(" ", "")
    if @cc_number =~ /\D/
      raise CCNumberError, "Credit Card numbers may not contain non digit
characters except spaces", caller
    end
    @cc_length = @cc_number.length
  end

  def card_type
    if @cc_length == 15
      if @cc_number[0..1].to_i == 34 or @cc_number[0..1].to_i == 37
        @cc_type = "American Express"
      end
    elsif @cc_length == 16 and @cc_number[0..3] == 6011
      @cc_type = "Discover"
    elsif @cc_length == 16 and (51..55) === @cc_number[0..1].to_i
      @cc_type = "MasterCard"
    elsif @cc_length == 16 or @cc_length == 13
      if @cc_number.index("4") == 0
        @cc_type = "Visa"
      end
    else
      @cc_type = "Unknown"
    end
  end

  def luhn
    ccn = @cc_number.reverse.scan(/\d/)
    ccn_luhn_sum = 0
    i = 0
    ccn.length.times do
      if i % 2 == 0
        ccn_luhn_sum += ccn[i].to_i
      else
        if ccn[i].to_i * 2 >= 10
          n = (ccn[i].to_i * 2).to_s
          ccn_luhn_sum += n[0].chr.to_i
          ccn_luhn_sum += n[1].chr.to_i
        else
          ccn_luhn_sum += ccn[i].to_i * 2
        end
      end
      i += 1
    end
    ccn_luhn_sum % 10 == 0 ? @luhn_valid = true : @luhn_valid = false
  end
end

ARGV.each do |n|
  card = CardValidate.new(n)
  puts "Card number: #{card.cc_number}"
  puts "Card type: #{card.cc_type}"
  puts "Luhn valid: #{card.luhn_valid}"
  puts
end
#######################

-d
--
darren kirby :: Part of the problem since 1976 :: http://badcomputer.org
"...the number of UNIX installations has grown to 10, with more expected..."
- Dennis Ritchie and Ken Thompson, June 1972

My solution is below.

# file: credit_card.rb
# author: Drew Olson

class CreditCard
  def initialize num
    @number = num
  end

  # check specified conditions to determine the type of card
  def type
    length = @number.size
    if length == 15 && @number =~ /^(34|37)/
      "AMEX"
    elsif length == 16 && @number =~ /^6011/
      "Discover"
    elsif length == 16 && @number =~ /^5[1-5]/
      "MasterCard"
    elsif (length == 13 || length == 16) && @number =~ /^4/
      "Visa"
    else
      "Unknown"
    end
  end

  # determine if card is valid based on Luhn algorithm
  def valid?
    digits = ''
    # double every other number starting with the next to last
    # and working backwards
    @number.split('').reverse.each_with_index do |d,i|
      digits += d if i%2 == 0
      digits += (d.to_i*2).to_s if i%2 == 1
    end

    # sum the resulting digits, mod with ten, check against 0
    digits.split('').inject(0){|sum,d| sum+d.to_i}%10 == 0
  end
end

if __FILE__ == $0
  card = CreditCard.new(ARGV.join.chomp)
  puts "Card Type: #{card.type}"
  if card.valid?
    puts "Valid Card"
  else
    puts "Invalid Card"
  end
end

···

--
Posted via http://www.ruby-forum.com/.

My second ever Ruby Quiz.
TIA for any suggestions for making it more Ruby-like.
/Bob

#!/usr/bin/env ruby -w

class CreditCard
  attr_reader :number, :type, :validity
  def initialize(cardnumber)
    @number = cardnumber.gsub(/\s/,'')
    @type = case @number
    when /^3[47]\d{13}$/ then "AMEX"
    when /^6011\d{12}$/ then "Discover"
    when /^5[12345]\d{14}$/ then "Mastercard"
    when /^4\d{12}$/ then "VISA"
    when /^4\d{15}$/ then "VISA"
    else "Unknown"
    end
    sum = 0
    digits = @number.to_s.split('').reverse.map {|i| i.to_i}
    digits.each_index {|i| i%2==0 ? sum+=add_digits(digits[i]) : sum+=add_digits(digits[i]*2)}
    @validity = sum%10 == 0 ? "Valid" : "Invalid"
  end
  def add_digits(n)
    return n.to_s.split('').inject(0) {|sum, i| sum += i.to_i}
  end
end #CreditCard

c = CreditCard.new(ARGV.join)
puts "#{c.number}: #{c.type}\t#{c.validity}"

Ruby Quiz <james@grayproductions.net> writes:

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.

#!ruby

def cardtype(n)
  case n.delete("^0-9")
  when /\A3[37]\d{13}\z/: "AMEX"
  when /\A6011\d{12}\z/: "Discover"
  when /\A5[1-4]\d{14}\z/: "Master Card"
  when /\A4\d{12}\d{3}?\z/: "Visa"
  else "Unknown"
  end
end

def luhn?(n)
  f = 2
  (n.delete("^0-9").reverse.split(//).map{|d|d.to_i}.
     inject(0) { |a,e| f=3-f; a + (e*f > 9 ? e*f-9 : e*f) } % 10).zero?
end

puts cardtype(ARGV.join)
puts luhn?(ARGV.join) ? "valid" : "invalid"

__END__

···

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

For completeness and to make this Quiz exercise valid to more people,
can anyone include the information for other major credit cards from
major countries? Japan: JCB, et. al., U.K.: Barclay, et. al. , etc...

UK had Switch and Solo till recently which are now rebranded under the
Maestro umbrella. They're not really credit cards and basically no
rules exist, you have to check tables to determine valid prefixes and
card number length. Additionally, Maestro card numbers may also be
Mastercard numbers, so they can't be uniquely identified as being
Maestro.

For JCB and Diner's:

JCB 3528-2589 Length: 16
Diners 3000-3029, 3040-3059, 36, 3815-3889, 389 Length: 14

Cheers,
   -Tim

It doesn't have to mean that:

$ ruby -e 'p ARGV.join' 1111 2222 3333 4444
"1111222233334444"

James Edward Gray II

···

On Apr 27, 2007, at 7:36 AM, Daniel Martin wrote:

Now granted, for accepting the card number from the command line this
will mean that on the command line the card number was quoted...

        +============+=============+===============+
        > Card Type | Begins With | Number Length |
        +============+=============+===============+
        > AMEX | 34 or 37 | 15 |
        +------------+-------------+---------------+
        > Discover | 6011 | 16 |
        +------------+-------------+---------------+
        > MasterCard | 51-55 | 16 |
        +------------+-------------+---------------+
        > Visa | 4 | 13 or 16 |
        +------------+-------------+---------------+

So is a card number like "4012 3456 7890" a valid Unknown, or an invalid Visa?

Visa Invalid

Always match the type first.

Similarly, for Unknown, should we accept any length, or go for the common 16?

Unknown is for any type that doesn't match, so it could be any length.

James Edward Gray II

···

On Apr 27, 2007, at 12:58 PM, Matthew Moss wrote:

You move back to the beginning after doubling the 2nd to last. You double every other one on the way back to the beginning. The 10 is 5*2 and the 14 is 7*2

···

On Apr 28, 2007, at 8:04 PM, Ari Brown wrote:

On Apr 27, 2007, at 7:59 AM, Ruby Quiz wrote:
<snippage>

  1. Starting with the next to last digit and continuing with every other
     digit going back to the beginning of the card, double the digit
  2. Sum all doubled and untouched digits in the number
  3. If that total is a multiple of 10, the number is valid

For example, given the card number 4408 0412 3456 7893:

  Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
  Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
  Step 3: 70 % 10 == 0

Thus that card is valid.

Uh, this is probably just affecting me but....

In his example, after he doubles the second to last digit (call it d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get his numbers, but a) where does he say that and b) where do the 10 and 14 come from?

Help me, hyperactive ruby posters!
~ Ari
English is like a pseudo-random number generator - there are a bajillion rules to it, but nobody cares.

The confusing part (that I didn't catch when I read it) is that step 2
is to sum all the *digits*, not the numbers.

So
step 1) 9 * 2 = 18
step 2) 1 + 8

He's not modding the result of the multiplication by 10, but rather
adding up the resulting component digits. The same occurs with the 10
and 14 (which Philip pointed out are the result of 5*2 and 7*2,
respectively).

···

On Apr 28, 9:04 pm, Ari Brown <a...@aribrown.com> wrote:

On Apr 27, 2007, at 7:59 AM, Ruby Quiz wrote:
> 1. Starting with the next to last digit and continuing with every
> other
> digit going back to the beginning of the card, double the digit
> 2. Sum all doubled and untouched digits in the number
> 3. If that total is a multiple of 10, the number is valid

> For example, given the card number 4408 0412 3456 7893:

> Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
> Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
> Step 3: 70 % 10 == 0

> Thus that card is valid.

Uh, this is probably just affecting me but....

In his example, after he doubles the second to last digit (call it
d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get
his numbers, but a) where does he say that and b) where do the 10 and
14 come from?

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.

Does that help?

-Mark

···

On Apr 28, 2007, at 8:04 PM, Ari Brown wrote:

For example, given the card number 4408 0412 3456 7893:

  Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
  Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
  Step 3: 70 % 10 == 0

Thus that card is valid.

Uh, this is probably just affecting me but....

In his example, after he doubles the second to last digit (call it d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get his numbers, but a) where does he say that and b) where do the 10 and 14 come from?

Raj Sahae wrote:

My second ever rubyquiz submission, so be nice.
Sorry if the submission is a little early (by about an hour and a half), but I won't be around a comp tomorrow.

Raj Sahae

I'll wait with my solution for a further hour :wink: but a comment to your solution:
try out: ruby ccc.rb 5508 0412 3456 7893
it should show: Diners Club US & Canada or Mastercard
but shows just Mastercaards

···

--
greets
                        (
                      )
                     (
               /\ .-"""-. /\
              //\\/ , \//\\
              >/\| ,;;;;;, |/\|
              //\\\;-"""-;///\\
             // \/ . \/ \\
            (| ,-_| \ | / |_-, |)
              //`__\.-.-./__`\\
             // /.-(() ())-.\ \\
            (\ |) '---' (| /)
             ` (| |) `
       jgs \) (/

  one must still have chaos in oneself to be able to give birth to a dancing star

Yet another Great Ruby Quiz :wink:
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 :wink:
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 :wink:
  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

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

This is my first rubyquiz. Here is my solution.

class CreditCard
   class CardType < Struct.new(:name, :regex, :accepted_lengths)
     def valid_length?(length)
       if accepted_lengths.is_a?(Array)
         return accepted_lengths.include?(length)
       else
         return accepted_lengths == length
       end
     end
   end

   CARD_TYPES = [CardType.new('AMEX', /^3[47]/, 15),
                 CardType.new('Discover', /^6011/, 16),
                 CardType.new('MasterCard', /^5[1-5]/, 16),
                 CardType.new('Visa', /^4/, [13, 16]),
                 CardType.new('Unknown', /.*/, 0)]

   def initialize(number)
     @number = number
     @card_type = CARD_TYPES.find {|t| @number =~ t.regex }
   end

   def card_type
     @card_type.name
   end

   def valid?
     return false unless @card_type.valid_length?(@number.length)
     numbers = @number.split(//).collect {|x| x.to_i}
     i = numbers.length - 2
     while i >= 0
       numbers[i] *= 2
       i -= 2
     end
     numbers = numbers.to_s.split(//)
     sum = 0; numbers.each {|x| sum += x.to_i}
     sum % 10 == 0
   end
end

abort "Usage: #{$0} card_number [...]" if ARGV.empty?
ARGV.each do |card_number|
   c = CreditCard.new(card_number)
   out = "#{card_number}: "
   out += (c.valid? ? "Valid " : "Invalid ")
   out += "#{c.card_type}"
   puts out
end

Wow, very nice solution! Impressive how short it is...

Here is my solution:

#!/usr/bin/env ruby -W

# Assign a regular expression that checks first characters and length
PROVIDERINFO = {
   "AMEX" => /^(34|37)\d{13}$/,
   "Discover" => /^6011\d{12}$/,
   "MasterCard" => /^5[1-5]\d{14}$/,
   "Visa" => /^4(\d{12}|\d{15})$/,
}

class CreditCard
   attr_reader :provider, :number

   def initialize(number)
     @number =
     # split credit card number and store in array
     number.scan(/\d/){|c| @number.push c.to_i}

     # Check Provider Infos
     @provider = "Unknown"
     PROVIDERINFO.each_pair {|k, v| @provider = k if @number.join.match(v) }
  end

   def luhn_passed?
     sum = 0
     @number.reverse.each_with_index do |num, i|
       # double the nummer if necessary and subtract 9 if the result
       # consists of 2 numbers (here same as summing up both numbers)
       num = num * 2 - ((num > 4) ? 9 : 0) if i % 2 == 1
       sum += num
     end
     sum % 10 == 0
   end

   def to_s
     "Creditcard number #{@number}\n" +
     " Provider: #{self.provider}\n" +
     " Luhn Algorithm #{'not ' unless self.luhn_passed?}passed"
   end
end

puts CreditCard.new(ARGV.join)

Regards,

Dennis Frommknecht

···

Am 02.05.2007 um 17:13 schrieb Christian Neukirchen:

Ruby Quiz <james@grayproductions.net> writes:

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.

#!ruby

def cardtype(n)
  case n.delete("^0-9")
  when /\A3[37]\d{13}\z/: "AMEX"
  when /\A6011\d{12}\z/: "Discover"
  when /\A5[1-4]\d{14}\z/: "Master Card"
  when /\A4\d{12}\d{3}?\z/: "Visa"
  else "Unknown"
  end
end

def luhn?(n)
  f = 2
  (n.delete("^0-9").reverse.split(//).map{|d|d.to_i}.
     inject(0) { |a,e| f=3-f; a + (e*f > 9 ? e*f-9 : e*f) } % 10).zero?
end

puts cardtype(ARGV.join)
puts luhn?(ARGV.join) ? "valid" : "invalid"

__END__

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org

Just one minor nit:

Christian Neukirchen <chneukirchen@gmail.com> writes:

def luhn?(n)
  f = 2
  (n.delete("^0-9").reverse.split(//).map{|d|d.to_i}.
     inject(0) { |a,e| f=3-f; a + (e*f > 9 ? e*f-9 : e*f) } % 10).zero?
end

You do know that (e*f > 9 ? e*f-9 : e*f) is equivalent to e*f%9,
right? So that makes this method:

def luhn?(n)
  f = 2
  (n.delete("^0-9").reverse.split(//).map{|d|d.to_i}.
     inject(0) { |a,e| f=3-f; a + e*f%9 } %10).zero?
end

(I still like my short version better, but too each his own)

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a[1] )
       puts "s=%q(#{s})",s.to_a[1]

Wow, those are some broad ranges.

···

On Apr 27, 2007, at 9:22 PM, Tim Becker wrote:

For completeness and to make this Quiz exercise valid to more people,
can anyone include the information for other major credit cards from
major countries? Japan: JCB, et. al., U.K.: Barclay, et. al. , etc...

UK had Switch and Solo till recently which are now rebranded under the
Maestro umbrella. They're not really credit cards and basically no
rules exist, you have to check tables to determine valid prefixes and
card number length. Additionally, Maestro card numbers may also be
Mastercard numbers, so they can't be uniquely identified as being
Maestro.

For JCB and Diner's:

JCB 3528-2589 Length: 16
Diners 3000-3029, 3040-3059, 36, 3815-3889, 389 Length: 14

Cheers,
  -Tim