【问题标题】:Implementing the Luhn algorithm in Ruby在 Ruby 中实现 Luhn 算法
【发布时间】:2012-02-08 05:40:10
【问题描述】:

我一直在尝试在 Ruby 中实现 Luhn 算法。我一直在执行以下步骤:

  • 该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整的帐号。此帐号必须通过以下测试:
    • 从最右边的校验位开始计数,然后向左移动,每第二位的值加倍。
    • 将产品的数字(例如,10 = 1 + 0 = 1、14 = 1 + 4 = 5)与原始数字中的非双位数字相加。
    • 如果总计模 10 等于 0(如果总计以 0 结尾)则根据 Luhn 公式该数字有效;否则无效。

http://en.wikipedia.org/wiki/Luhn_algorithm

这是我想出的:

 def validCreditCard(cardNumber)
    sum = 0
    nums = cardNumber.to_s.split("")
    nums.insert(nums.size, "x")
    nums.reverse!
    nums.each_with_index do |n, i|
        if !n.eql?("x")
            sum += (i % 2 == 0) ? n.to_i : n.to_i * 2
        end
    end
    if (sum % 10) == 0
        return true
    else
        return false
    end
end

但是,每次我测试它都会返回false。我不确定我做错了什么。

【问题讨论】:

  • 请注意,方法末尾的if 语句非常冗长且不必要。如果逻辑正确,您可以简单地将其全部替换为 sum%10 == 0; this 的计算结果为truefalse,方法中最终表达式的值是方法的返回值。
  • 这是有道理的。谢谢你帮助我。
  • 现在有一个宝石:rubygems.org/gems/luhn
  • gem 中的解决方案不是很优雅

标签: ruby


【解决方案1】:

这是一个快速有效的方法:

def credit_card_valid?(account_number)
  digits = account_number.chars.map(&:to_i)
  check = digits.pop

  sum = digits.reverse.each_slice(2).flat_map do |x, y|
    [(x * 2).divmod(10), y || 0]
  end.flatten.inject(:+)

  check.zero? ? sum % 10 == 0 : (10 - sum % 10) == check
end

credit_card_valid? "79927398713" #=> true
credit_card_valid? "79927398714" #=> false

【讨论】:

  • +1 用于非常惯用的实现。请注意,您可以在 Ruby 1.9+ 中使用 chars 代替 scan(/./)。另请参阅flat_map 1.9+
  • @Phrogz:我知道他们两个,但旧习惯有时很难改掉。 :-)
  • 使用each_slice(2)的问题是,如果account_number是奇数,当你尝试运行inject(:+)方法时会遇到问题。
  • 这只是我,还是这段代码不起作用? (我在inject(:+) 部分有一个'没有将Fixnum隐式转换为数组'。为了工作,我必须将其更改为end.flatten.inject(:+)
  • @bbozo 已为您修复。
【解决方案2】:

我在运行上述很多代码时遇到了问题,所以我继续开发了一个解决方案。下面是我的解决方案,以及测试代码,可以使用 Ruby-cores AWESOME minitest 库运行。

require "minitest"
require "minitest/autorun"

# Public: Validates number against Luhn 10 scheme
#
# Luhn Algo ripped from: http://en.wikipedia.org/wiki/Luhn_algorithm
# 1. From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if product of this doubling operation is greater than 9 (e.g., 7 * 2 = 14).
# 2. Sum the digits of the products (e.g., 10: 1 + 0 = 1, 14: 1 + 4 = 5) together with the undoubled digits from the original number.
# 3. If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; else it is not valid.
#
# Returns true or false
def luhn_valid?(cc_number)
  number = cc_number.
    gsub(/\D/, ''). # remove non-digits
    reverse  # read from right to left

  sum, i = 0, 0

  number.each_char do |ch|
    n = ch.to_i

    # Step 1
    n *= 2 if i.odd?

    # Step 2
    n = 1 + (n - 10) if n >= 10

    sum += n
    i   += 1
  end

  # Step 3
  (sum % 10).zero?
end

describe "Luhn Algorithm" do
  # Cards ripped from paypal: http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
  CC_NUMBERS = {
    "American Express" => "378282246310005",
    "American Express 2" => "371449635398431",
    "American Express Corporate" => "378734493671000",
    "Australian BankCard" => "5610591081018250",
    "Diners Club" => "30569309025904",
    "Diners Club 2" => "38520000023237",
    "Discover" => "6011111111111117",
    "Discover 2" => "6011000990139424",
    "JCB" => "3530111333300000",
    "JCB 2" => "3566002020360505",
    "MasterCard" => "5555555555554444",
    "MasterCard 2" => "5105105105105100",
    "Visa" => "4111111111111111",
    "Visa 2" => "4012888888881881",
    "Visa 3" => "4222222222222",
  }

  it "returns true for valid numbers" do
    assert CC_NUMBERS.values.all? { |number| luhn_valid?(number) }
  end

  it "returns false for invalid numbers" do
    CC_NUMBERS.values.each do |number|
      me_turn_bad = (number.to_i + 1).to_s
      refute luhn_valid?(me_turn_bad)
    end
  end
end

【讨论】:

    【解决方案3】:

    上面有一些很酷的答案。我想编写自己的解决方案;为了后代,我在这里添加它。

    def number_is_luhn_valid?(credit_card_number)
      cc_digits = credit_card_number.to_s.reverse.chars.map(&:to_i)
      check_sum = 0
    
      cc_digits.each_slice(2) do |odd, even|
        check_sum += odd
        next unless even
        even *= 2
        even = even.divmod(10).inject(:+) if even > 9
        check_sum += even
      end
    
      return check_sum.modulo(10) == 0
    end
    

    如果你有反馈,总是很乐意听到!

    【讨论】:

      【解决方案4】:

      我猜问题是您没有考虑通过在数组末尾插入“x”获得的索引移位,因此您将错误的数字加倍。

      【讨论】:

        【解决方案5】:

        这是您的解决方案:

        def validate(cardNumber)
          nums = cardNumber.to_s.split("")
          checkdigit = nums[nums.length - 1]
          nums[nums.length - 1] = 0
          nums.reverse!
          sum=0
          for i in 1..nums.length
            if i%2==0 
              sum = sum + nums[i].to_i  
              next
            end
            nums[i] = (nums[i].to_i*2 < 10 ) ? (nums[i].to_i*2) : (nums[i].to_i*2 - 9)
            sum = sum + nums[i].to_i
          end
          puts (10 - sum%10).to_i == checkdigit.to_i
        end
        
        validate(79927398713)
        

        它没有优化,但你可以自己做这些小改动:)

        【讨论】:

          【解决方案6】:

          我已经稍微改变了你的实现。现在可以了

          def validCreditCard(cardNumber)
            sum = 0
            nums = cardNumber.to_s.split("")
            nums.each_with_index do |n, i|
              sum += if (i % 2 == 0)
                       n.to_i * 2 >9 ? n.to_i*2-9 : n.to_i*2
                     else
                       n.to_i
                     end
            end
            if (sum % 10) == 0
              return true
            else
              return false
            end
          end
          

          【讨论】:

            【解决方案7】:

            感谢 Michael 帮助我实施我的解决方案。

            它可能效率不高,但它有效,我了解 Luhn 和我的实施。

            我还在学习,但这里是:

             def validCreditCard?(cardNumber)
                sum = 0 
                digits = []
                nums = cardNumber.to_s.split("")
                nums.reverse.each_slice(2) do |n|
                    digits << (n.last.to_i * 2)
                    digits << n.first.to_i
                end
                digits.each do |n|
                    stringNum = n.to_s
                    if stringNum.length == 2
                        tempNums = stringNum.split("")
                        sum += tempNums.first.to_i + tempNums.last.to_i
                    else
                        sum += stringNum.to_i
                    end
                end
                sum%10 == 0
            end
            

            【讨论】:

            • 很高兴为您提供帮助 :-) 遵循 SO 礼节,如果我的回答对您有所帮助,您应该使用投票箭头下方的小勾号接受它,以便其他人知道该问题是回答。
            • 没问题。再次感谢。另外,Phrogz 你帮助我的代码更干净。
            【解决方案8】:

            我偶然发现了这个用于 Luhn 算法的 Ruby gem: https://github.com/joeljunstrom/ruby_luhn

            【讨论】:

              【解决方案9】:
                  #!/usr/bin/env ruby
              number = ARGV[0]
              ## making array from input number having each digit at separate index
              digits = number.chars.map(&:to_i)
              check = digits.pop
              
              sum = digits.reverse.each_slice(2).flat_map do |x, y|
                [(x * 2).divmod(10), y || 0]
              end.flatten.inject(:+)
              ## validating if sum is nil then assiging it zero, when we pass single digit like 4 or 5 or 9 then sum will be nil
              sum = sum ? sum : 0
              puts (check.zero? ? sum % 10 == 0 : (10 - sum % 10) == check) ? "#{number} is valid" : "#{number} is invalid"
              

              如果您输入单个数字作为输入,Michael 给出的解决方案将失效,此代码将处理所有情况。

              【讨论】:

                【解决方案10】:

                我们使用 Luhn 算法来检查银行卡的有效性。

                符合 ISO/IEC 7812-1:2017 的银行卡号 (PAN) 长度为 10 到 19。

                因此,我的解决方案是:

                def card_valid?(card_number)
                  return false unless card_number.match /\A\d{10,19}\z/
                  sum = 0
                  card_number.reverse.chars.map(&:to_i).each.with_index(1) do |d, i|
                    d *= 2 if i.even?
                    d -= 9 if d > 9
                    sum += d
                  end
                  (sum % 10).zero?
                end
                
                card_valid? '1115' #=> false
                card_valid? '4417123456789112' #=> false
                card_valid? '4408041234567893' #=> true
                

                【讨论】:

                  【解决方案11】:

                  一行(语句)中有我的黑客实现。

                  输入:字符串

                  输出:正确信用卡号的“有效”。

                  puts (n=gets).size == 16 && n.reverse.chars.map(&:to_i).map.with_index{|x,i| i.odd?? x>4 ? x*2-9: x*2: x}.sum%10==0 ? 'valid': 'not valid'
                  

                  【讨论】:

                    最近更新 更多