【问题标题】:Refactor Ruby code for Luhn algorithm重构 Luhn 算法的 Ruby 代码
【发布时间】:2015-04-30 11:12:11
【问题描述】:

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

该公式根据其包含的校验位验证一个数字,该校验位 通常附加到部分帐号以生成完整的帐号 帐号。此帐号必须通过以下测试:

  1. 从最右边的数字(即校验位)开始,向左移动,每隔一个数字加一倍;如果这个加倍运算的乘积大于 9(例如,8 × 2 = 16),则将乘积的数字相加(例如,16: 1 + 6 = 7, 18: 1 + 8 = 9)。李>
  2. 取所有数字的总和。
  3. 如果总计模 10 等于 0(如果总计以 0 结尾)则根据 Luhn 公式该数字有效;否则无效。

假设一个帐号为“7992739871”的示例将具有 添加校验位,格式为 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

校验位(x)是通过计算数字的总和得到的 计算该值的 9 倍模 10(以方程形式,(67 × 9 mod 10))。算法形式:

  1. 计算数字的总和 (67)。
  2. 乘以 9 (603)。
  3. 最后一位数字 3 是校验位。因此,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

显然,我在所有这些方面都是一个等级菜鸟。但我感谢任何帮助,包括正确的风格!

【问题讨论】:

  • 第一步是使用有助于强制缩进的编辑器。这对编写正确的代码大有帮助。
  • 谢谢。我正在使用 pico,它没有任何帮助!
  • 优秀的编辑器很多。两个支柱是 vim 和 emacs。它们有点学习曲线,但它们可以在多个平台上运行,所以一旦你学习了一个或另一个,你就可以在其他机器上使用相同的命令和配置。我在 Mac OS、Windows 和 Linux 上使用 vim,都具有相同的配置。 Sublime Text Editor 和 Textmate 也都不错; Sublime 正在不断发展中,Textmate 似乎已经过时了。

标签: ruby refactoring credit-card


【解决方案1】:

建议:

  1. 尝试将您的代码封装在一个类中并提供直观的公共 API。在私有方法中隐藏算法的内部细节。
  2. 将规则分解为最多 5 行的类中的小方法,谨慎地破坏此规则。关注Sandi Metz Rules
  3. 研究问题,找到与问题相关的域名;用它来命名小方法。
  4. 注重可读性。请记住这句话:“必须编写程序供人们阅读,并且只是偶然地供机器执行。” 来自 SICP 的 Hal Abelson。
  5. 阅读Ruby style guide以改进代码格式;是的,找一个更好的编辑器。
  6. 遵循这些可能会使代码更加冗长。但它会提高可读性并有助于维护。此外,如果您甚至在个人项目中也倾向于遵循它,那么这个过程将深深烙印在您的心中,并很快成为您的第二天性。

考虑到这些,对问题进行以下尝试:

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

【讨论】:

    【解决方案2】:

    如何使用嵌套注入方法

     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}}
    

    【讨论】:

      【解决方案3】:

      我会这样实现该算法:

      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
      

      好吧,该代码适用于 Wikipedia 中的示例 :)

      以下是给你的一些建议:

      • 在你的函数中去掉打印/放置到标准输入,只返回一个 价值。对于这个函数,布尔值 true/false 很好。
      • 红宝石社区 使用“?”在返回 false/true 的方法名称中
      • 别忘了 正确格式化您的代码,但也许您还没有学会如何在 * 上执行此操作(我还没有:)
      • 使用 2 个空格来缩进代码

      【讨论】:

      • 这非常简单易懂。并感谢您的提示。我不明白倒数第二行的语法:end % 10 == 0 我明白它的作用,但不明白它如何获得它正在检查的值。谢谢!
      • 嘿,% 10 之前的整个结构会返回一个数字,如果来自*的复杂乘法/求和结果。所以我可以把它分配给一些变量,比如sum = digits.each_with_index...' and then use it like sum % 10 == 0`。如果信用卡号有效,该函数返回 true,否则返回 false。
      最近更新 更多