How is a check digit calculation done in Ruby?
I'm tr开发者_如何学Pythonying to build a check-digit calculation in Ruby for FedEx tracking numbers.
Here is info and the steps for the check-digit calculation:
- Digit positions are labeled from right to left.
- Digit 1 is the check character.
- Digits 16 through 22 are not used.
Steps:
- Starting from position 2, add up the values of the even numbered positions.
- Multiply the results of step one by three.
- Starting from position 3, add up the values of the odd numbered positions. Remember – position 1 is the check digit you are trying to calculate.
- Add the result of step two to the result of step three.
- Determine the smallest number which when added to the number from step four results in a multiple of 10. This is the check digit.
Here is an example of the process (provided by FedEx):
So, how do I implement this in Ruby?
When you have your number as string (or if you have your digit as integer, just #to_s
on it and get string), and then you can simply extract digits from there with:
number_string[idx].to_i
or if you use Ruby 1.8
number_string[idx..idx].to_i
#to_i
is to convert it to integer, so you can add it to others. Then just proceed with steps provided to calculate your number.
All you have to do to implement it is correctly map positions provided in instruction to idx
index position in your string representation of number. Just do it on paper with counting in head or use negative idx (it counts from end of the string) in Ruby.
EDIT:
The solution could be something like this:
bar_code_data = "961102098765431234567C"
digits_with_position = bar_code_data.reverse[1..14].split(//).map(&:to_i).zip(2..1/0.0)
this goes as follow:
reverse
- reverse string, so now we can count from left to right instead of reverse[1..14]
- select substrig of characters, which we're interested in (Ruby counts from 0)split(//)
- split one string into substrings of length 1 character, in other words - separate digitsmap(&:to_i)
- call #to_i on every element of array, in other words convert to integerzip(2..1/0.0)
- add position starting from 2 to Infinity, to every element
Now we should have something like this:
[[7, 2], [6, 3], [5, 4], [4, 5], [3, 6], [2, 7], [1, 8], [3, 9], [4, 10], [5, 11], [6, 12], [7, 13], [8, 14], [9, 15]]
sum = digits_with_position.map{|i| i[0] * (i[1].even? ? 3 : 1)}.reduce(+:)
We made little change in algorithm, which should not be hard to you to follow:
instead of:
sum = (in[2] + in[4] + in[6] + ...)*3 + (in[3] + in[5] + in[7] + ...)
we made:
sum = in[2]*3 + in[3]*1 + in[4]*3 + in[5]*1 + in[6]*3 + in[7]*1 + ...
which is the same result, but with changed order of operations.
Also:
map {|i| ... }
- map every value of list, i is tuple in our case, pair of [digit,pos]i[1].even?
- check if position is eveni[1].even? ? 3 : 1
- for even position use 3, for opposite (odd) use just 1reduce(:+)
- reduce resulting array to single value using + operation (add all results)
Now fun part :-)
check_code = 10 - (sum % 10)
sum % 10
- module 10 of sum value, return reminder of division sum by 10, which in our case is last digit10 - (sum % 10)
- complement to nearest not smaller multiple of 10
There is error in description, because if you would have 130 as result, then next bigger multiple of 10 is 140 and difference is 10, which is not correct result for digit (it should probably be 0).
Other faster solution would be like this (unroll all loops, just hardcode everything):
d = "961102098765431234567C".split(//) # avoid having to use [-2..-2] in Ruby 1.8
sum_even = d[-2].to_i + d[-4].to_i + d[-6].to_i + d[-8].to_i + d[-10].to_i + d[-12].to_i + d[-14].to_i
sum_odd = d[-3].to_i + d[-5].to_i + d[-7].to_i + d[-9].to_i + d[-11].to_i + d[-13].to_i + d[-15].to_i
sum = sum_even * 3 + sum_odd
check_code = 10 - sum % 10
It's just dead simple solution, not worth explaining, unless someone asks for it
Pass your number to below method and it will return the number appended with a checksum digit. Reference used from: https://www.gs1.org/services/how-calculate-check-digit-manually
def add_check_digit(code_value)
sum = 0
code_value.to_s.split(//).each_with_index{|i,index| sum = sum + (i[0].to_i * ((index+1).even? ? 3 : 1))}
check_digit = sum.zero? ? 0 : (10-(sum % 10))
return (code_value.to_s.split(//)<<check_digit).join("")
end
精彩评论