重构Luhn算法的Ruby代码

时间:2015-02-27 16:51:32

标签: ruby refactoring credit-card

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

  

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

     
      
  1. 从最右边的数字,即校验位,向左移动,每秒数字的值加倍;如果此倍增操作的乘积大于9(例如,8×2 = 16),则将产品的数字相加(例如,16:1 + 6 = 7,18:1 + 8 = 9)。
  2.   
  3. 取所有数字的总和。
  4.   
  5. 如果总模数10等于0(如果总数以零结束)则该数字根据Luhn公式有效;否则它无效。
  6.         

    假设一个帐号为“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)是通过计算数字之和得到的   以10的方式计算该值的9倍(方程式,(67×9 mod   10))。在算法形式:

         
        
    1. 计算数字之和(67)。
    2.   
    3. 乘以9(603)。
    4.   
    5. 最后一位数字3是校验位。因此,x = 3。
    6.   

以下是我的实施,它可行,但可能会好很多,我相信。

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

显然,我在所有这一切都是排名。但我感谢任何帮助,包括适当的风格!

3 个答案:

答案 0 :(得分:2)

建议:

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

答案 1 :(得分:0)

如何使用嵌套注入方法

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

答案 2 :(得分:0)

我会像那样实现那个算法:

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

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

以下是一些建议:

  • 在你的函数中删除print / puts到stdin,只需返回一个 值。对于这个函数,boolean true / false是好的。
  • 红宝石社区 用'?'在返回false / true的方法名称中
  • 别忘了 正确格式化你的代码,但也许你可能还没有学会如何在Stackoverflow上做到这一点(我还没有:)
  • 使用2个空格来缩进代码