重构 Luhn 算法的 Ruby 代码

Refactor Ruby code for Luhn algorithm

帮我重构实现Luhn algorithm,描述如下:

The formula verifies a number against its included check digit, which is usually appended to a partial account number to generate the full account number. This account number must pass the following test:

  1. From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if the product of this doubling operation is greater than 9 (e.g., 8 × 2 = 16), then sum the digits of the products (e.g., 16: 1 + 6 = 7, 18: 1 + 8 = 9).
  2. Take the sum of all the digits.
  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.

Assume an example of an account number "7992739871" that will have a check digit added, making it of the form 7992739871x:

  • Account number 7 9 9 2 7 3 9 8 7 1 x
  • Double every other 7 18 9 4 7 6 9 16 7 2 -
  • Sum of digits 7 9 9 4 7 6 9 7 7 2 =67

The check digit (x) is obtained by computing the sum of digits then computing 9 times that value modulo 10 (in equation form, (67 × 9 mod 10)). In algorithm form:

  1. Compute the sum of the digits (67).
  2. Multiply by 9 (603).
  3. The last digit, 3, is the check digit. Thus, x=3.

以下是我的实现,它可以工作,但我相信可以做得更好。

def credit_check(num)
verify = num.to_s.split('').map(&:to_i)

half1 = verify.reverse.select.each_with_index { |str, i| i.even? }
half1 = half1.inject(0) { |r,i| r + i }

# This implements rule 1
half2 = verify.reverse.select.each_with_index { |str, i| i.odd? }     
double = half2.map { |n| n * 2 }
double = double.map { |n| n.to_s.split('') }
double = double.flatten.map(&:to_i) 
double = double.inject(0) { |r,i| r + i }

final = double + half1   

puts final % 10 == 0 && (num.to_s.length > 12 && num.to_s.length < 17) ? "VALID" : "INVALID"
end

显然,我在所有这些方面都是菜鸟。但我感谢任何帮助,包括适当的风格!

如何使用嵌套注入方法

 half2  = verify.reverse.select.each_with_index { |str, i| i.odd? }
 double = half2.map { |n| n * 2 }

 double = double.inject(0){|x,y| x + y.to_s.split("").inject(0){|sum, n| sum + n.to_i}}

建议:

  1. 尝试将您的代码封装在 class 中并提供直观的 public API。在私有方法中隐藏算法的内部细节。
  2. 在最多5行的class中将规则分解成小方法,谨慎地打破这个规则。关注 Sandi Metz Rules.
  3. 研究问题并找到与问题相关的域名;用它来命名小方法。
  4. 注重可读性。记住这句话:"Programs must be written for people to read, and only incidentally for machines to execute." 来自 SICP.
  5. 的 Hal Abelson
  6. 阅读Ruby style guide以改进代码格式;是的,找一个更好的编辑器。
  7. 遵循这些似乎会使代码更加冗长。但它会提高可读性并有助于维护。此外,如果您甚至在个人项目中也倾向于遵循它,那么这个过程将深深地刻在您的身上,很快就会成为第二天性。

考虑到这些,通过以下尝试解决问题:

class CreditCard
  VALID_LENGTH_RANGE = 12..17

  def initialize(number)
    @number = number.to_s
  end

  def valid?
    valid_length? && check_sum_match?
  end

  private

  def valid_length?
    VALID_LENGTH_RANGE.include? @number.length
  end

  def check_sum_match?
    check_sum.end_with? check_digit
  end

  def check_sum
    digits = check_less_number
             .reverse
             .each_char
             .each_with_index
             .map do |character, index|
      digit = character.to_i
      index.even? ? double_and_sum(digit) : digit
    end

    digits.reduce(:+).to_s
  end

  def check_less_number
    @number[0..-2]
  end

  def check_digit
    @number[-1]
  end

  def double_and_sum(digit)
    double = digit * 2
    tens = double / 10
    units = double % 10

    tens + units
  end
end

因此您可以按如下方式使用它:

CreditCard.new(222222222224).valid? # => true
CreditCard.new(222222222222).valid? # => false

我会像这样实现该算法:

def credit_card_valid?(num)
  digits = String(num).reverse.chars.map(&:to_i)
  digits.each_with_index.reduce(0) do |acc, (value, index)|
    acc + if index.even?
            value
          else
            double_value = value * 2
            if double_value > 9
              double_value.to_s.split('').map(&:to_i).reduce(&:+)
            else
              double_value
            end
          end
  end % 10 == 0
end

好吧,该代码适用于维基百科中的那些示例:)

这里有一些建议给你:

  • 在你的函数中去掉 prints/puts 到标准输入,只是 return 价值。对于这个函数 boolean true/false 很好。
  • ruby 社区 使用“?”在 return false/true
  • 的方法名称中
  • 别忘了 正确格式化你的代码,但也许你还没有学会如何在 Whosebug 上做它(我还没有:)
  • 使用 2 个空格缩进代码