ifyouseewendy/rmb

View on GitHub
lib/rmb_chinese_yuan.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require 'rmb_chinese_yuan/version'

begin
  require 'pry'
rescue LoadError
end

class RMB
  NUMBERS       = %w(零 壹 贰 叁 肆 伍 陆 柒 捌 玖)
  PART_UNIT     = %w(仟 佰 拾)
  INTEGER_UNIT  = %w(亿 万 元)
  DECIMAL_UNIT  = %w(整 角 分)

  attr_reader :integer, :decimal, :parts, :words

  def initialize(money)
    @integer, @decimal = preprocess(money)
  end

  def convert
    join(convert_integer, convert_decimal)
  end

  private

    # Private: Process for type conversion, ',' substitution, and '.' split.
    def preprocess(money)
      money = money.to_s

      fail ArgumentError, "Not a valid money<#{money}>" \
        unless money.match(/^[\d|,|\.]+/)

      integer, decimal = money.delete(',').split('.')
      decimal = decimal ? decimal[0, 2] : ''

      fail ArgumentError, "Integer part of money<#{money}> is invalid" \
        unless integer.match(/^\d*$/)

      fail ArgumentError, "Decimal part of money<#{money}> is invalid" \
        unless decimal.match(/^\d*$/)

      fail ArgumentError, "Integer part of money<#{money}> is longer than 12" \
        if integer.length > 12

      [integer, decimal]
    end

    def convert_integer
      split_into_parts
      read_into_words
      join_words
    end

    def convert_decimal
      return DECIMAL_UNIT[0] if decimal.to_i.zero?

      jiao, fen = split_into_digits(decimal, direction: :tail, count: 2)

      if jiao.zero? && fen.nonzero?
        [NUMBERS[0], NUMBERS[fen], DECIMAL_UNIT[2]].join
      else
        res = [NUMBERS[jiao], DECIMAL_UNIT[1]].join
        res << [NUMBERS[fen],  DECIMAL_UNIT[2]].join if fen.nonzero?
        res
      end
    end

    def join(integer_words, decimal_words)
      if integer.to_i.zero? && decimal.to_i.nonzero?
        decimal_words
      else
        [integer_words, decimal_words].join
      end
    end

    # Split money into three parts, each part is under 9999
    def split_into_parts
      number = integer.to_i

      @parts = [
        number / 10**8,
        (number / 10**4) % 10**4,
        number % 10**4
      ]
    end

    def read_into_words
      @words = parts.reduce([]){ |ar, part| ar << read_integer(part) }
    end

    def read_integer(number)
      return NUMBERS[0] if number.zero?

      numbers = split_into_digits(number)

      numbers.each_with_index.reduce('') do |str, (n, i)|
        if n.nonzero?
          str << [NUMBERS[n], PART_UNIT[i]].join
        elsif !str.empty? && i + 1 < 4 && numbers[i+1].nonzero?
          str << NUMBERS[0]
        else
          str
        end
      end
    end

    def split_into_digits(number, direction: :head, count: 4)
      digits = number.to_s.chars.map(&:to_i)
      if direction == :head
        digits.unshift 0 while digits.count < count
      else
        digits.push 0 while digits.count < count
      end
      digits
    end

    def join_words
      res = [part_yi, part_wan, part_ge].join
      res << INTEGER_UNIT[2]
    end

    def part_yi
      [words[0], INTEGER_UNIT[0]].join unless parts[0].zero?
    end

    def part_wan
      yi, wan, ge = parts

      if wan.zero?
        if yi.nonzero? && ge.nonzero?
          NUMBERS[0]
        end
      else
        res = [words[1], INTEGER_UNIT[1]]
        res.unshift NUMBERS[0] if yi.nonzero? && (wan / 1000).zero?
        res.join
      end
    end

    def part_ge
      yi, wan, ge = parts

      if ge.zero?
        if yi.zero? && wan.zero?
          words[2]
        end
      else
        res = [words[2]]
        res.unshift NUMBERS[0] if wan.nonzero? && (ge / 1000).zero?
        res.join
      end

    end
end