Having a little issue with my code to see if a credit card number adheres to the Luhn Algorithm. The code is returning true when the Credit Card is divisible by 10, but also is returning true when the CC number is not divisible by 10. I have printed out the final sum to make sure the numbers were actually adding to the sum variable, and they seem to be.. Below is my code. I know it can be cleaner, but at this stage I would like to see it work first.
def check_card
c_num= []
sum=0
s_numbers=@card_numbers.to_s.reverse.split("")
s_numbers.each_slice(2) do |x|
c_num << (x.last.to_s.to_i*2)
c_num << (x.first.to_s.to_i)
end
c_num.each do |num|
if num.to_i > 9
sum+= (num.to_i % 10)+1
else
sum += num.to_i
end
end
sum % 10==0
end
Here is how it is being called:
it 'returns false for a bad card' do
card = CreditCard.new(4408041234567892)
card.check_card.should eq false
end
This is your code, commented and adapted to behave like the wikipedia description.
def check_card(str) #creditcardnumber as argument
c_num = []
sum = 0
s_numbers = str.split("") #no reversing. str.split("").map(&:to_i) would save a lot of to_i's later on...
checksum = s_numbers.pop.to_i #chop off last digit, store as checksum
s_numbers.each_slice(2) do |x|
c_num << (x.last.to_s.to_i*2)
c_num << (x.first.to_s.to_i)
end
c_num.each do |num|
if num.to_i > 9
sum+= (num.to_i % 10)+1
else
sum += num.to_i
end
end
(sum * 9) % 10 == checksum
end
p check_card("79927398713") #=> true
Now that another answer has appeared I will offer a suggested coding. This does not answer your question, but I thought it might be of interest and couldn't very well put it in a comment, because of formatting limitations.
def valid?(card)
return false unless card =~ /^\d+$/ # Ensure card contains only digits
arr = card.split('').reverse.each_with_index.map {|d, index| (index.odd? ? 2*(d.to_i) : d.to_i)}
(arr.join.split('').inject(0) {|tot, d| tot + d.to_i}) % 10 == 0
end
valid?("1234567890123456") => false
It seems you're confusing the output of the test with the output of the check_card
method. You've confirmed that sum == 69
, so sum % 10 == 0
should return false
(unless Ruby's math is broken), so your check_card
method should also return false
— add the line puts card.check_card
after the line card = CreditCard.new(4408041234567892)
(but before the next line) in your testing block to show the value returned.
The next line, card.check_card.should eq false
, asserts that the returned value "should equal" false
— that is, it expects check_card
to be false
for that value of @card_numbers
, and will return true
if that's the case. I suspect that you're seeing the test come up true
and taking that to mean the method is returning the incorrect value of true
.
Without seeing the output of the test, this is only speculation, of course. However, your code seems correct and, for me, gives the correct result.
posting my answer because I thought it was clean:
def valid?(card_number)
return false unless card_number !~ /\D/
card_numbers = card_number.reverse.split("").map(&:to_i)
sum = 0
card_numbers.each_with_index do |num, i|
if i.even?
sum += num
else
sum += (num *= 2) > 9 ? num.divmod(10).inject(:+) : num
end
end
sum % 10 == 0
end