I'm trying to build a check-digit calculation in Ruby for FedEx tracking numbers.
Here is info and the steps for the check-digit calculation:
Steps:
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:
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 elementNow 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 10There 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).
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