rubyalgorithmcheck-digit

How is a check digit calculation done in Ruby?


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:

  1. Starting from position 2, add up the values of the even numbered positions.
  2. Multiply the results of step one by three.
  3. 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.
  4. Add the result of step two to the result of step three.
  5. 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): enter image description here

So, how do I implement this in Ruby?


Solution

  • 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:

    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:

    Now fun part :-)

    check_code = 10 - (sum % 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