Please Forward: Ruby Quiz Submission

From: James Koppel <darmaniiii@yahoo.com>
Date: April 27, 2007 7:53:31 PM CDT
To: submission@rubyquiz.com
Subject: Please Forward: Ruby Quiz Submission

class Array
  def map_with_index
    each_with_index { |el, i|
      self[i] = yield el, i}
  end
end

#Contains the card types keyed by regular
#expressions that match said card types
$cardTypes = {/^3[47]\d{13}$/ => "AMEX",
              /^6011\d{12}$/ => "Discover",
              /^5[1-5]\d{14}$/ => "MasterCard",
              /^4\d{12}(\d{3})?$/ => "Visa"}

#Returns the card type
def cardType(cardNumber)
  $cardTypes.each_key {|format|
    return $cardTypes[format] if cardNumber =~ format}
  "Unknown"
end

require 'enumerator'

#Returns whether it passes Luhn validation
def luhn?(cardNumber)
  cardNumber.split(//).map_with_index do |el, i|
    #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
    #
    #This means that, if the length is even, even indices
    #are doubled, and vice versa
    if i % 2 == cardNumber.length % 2
      #Doubled digits will have to be split again
      (el.to_i * 2).to_s.split(//).map{|n| n.to_i}
    else
      el.to_i
    end
  #2) Sum all numbers
  end.flatten.enum_for.inject(0){|sum, num|sum + num } %
  #3)Check for multiple of 10
  10 == 0
end

ARGV.each do |arg|
  puts cardType(arg)
  #1-line way of converting booleans to words
  puts ({false => "Invalid", true => "Valid"}[luhn?(arg)])
end

Ahhh...imagining that irresistible "new car" smell?
Check out new cars at Yahoo! Autos.

···

Begin forwarded message:

Here's my solution:

#!/usr/bin/env ruby

def type(card)
  if card =~ /\A4(\d{12}|\d{15})\Z/
    "VISA"
  elsif card =~ /\A3(4|7)\d{13}\Z/
    "AMEX"
  elsif card =~ /\A5[1-5]\d{14}\Z/
    "MasterCard"
  elsif card =~ /\A6011\d{12}\Z/
    "Discover"
  else
    "Unknown"
  end
end

def luhn_valid?(card)
  digits = card.unpack("c#{card.length}").map { |c| c.chr.to_i }
  sum = 0
  digits.each_with_index do |n, i|
    val = if i % 2 == digits.length % 2
      2 * n
    else
      n
    end

    sum += if val >= 10
      1 + (val % 10)
    else
      val
    end
  end
  sum % 10 == 0
end

def valid?(card)
  card_type = type(card)
  puts "#{card_type} - #{ (card_type != "Unknown" && luhn_valid?
(card)) ? "Valid" : "Invalid" }"
end

valid?(ARGV[0])