+和<<之间的差异在Ruby中

时间:2015-02-09 14:40:56

标签: ruby

下面是两个相同的类,它们与运算符+<<不同。这些可以在注入方法中找到。在+情况下,测试通过,<<中的一些测试失败。为什么呢?

class Integer
  ROMAN_NUMERALS = {
    0 => '',
    1 => 'I',  2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V', 6 => 'VI', 7 => 'VII', 8 => 'VIII', 9 => 'IX',
    10 => 'X',  20 => 'XX', 30 => 'XXX', 40 => 'XL', 50 => 'L', 60 => 'LX', 70 => 'LXX', 80 => 'LXXX', 90 => 'XC',
    100 => 'C',  200 => 'CC', 300 => 'CCC', 400 => 'CD', 500 => 'D', 600 => 'DC', 700 => 'DCC', 800 => 'DCCC', 900 => 'CM',
    1000 => 'M', 2000 => 'MM', 3000 => 'MMM'
  }

  def to_roman
    to_s.reverse.chars.each_with_index.inject("") do |roman_numeral, (character, index)|
      ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral
    end
  end
end

我在运行

时会得到不同的结果
class Integer
  ROMAN_NUMERALS = {
    0 => '',
    1 => 'I',  2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V', 6 => 'VI', 7 => 'VII', 8 => 'VIII', 9 => 'IX',
    10 => 'X',  20 => 'XX', 30 => 'XXX', 40 => 'XL', 50 => 'L', 60 => 'LX', 70 => 'LXX', 80 => 'LXXX', 90 => 'XC',
    100 => 'C',  200 => 'CC', 300 => 'CCC', 400 => 'CD', 500 => 'D', 600 => 'DC', 700 => 'DCC', 800 => 'DCCC', 900 => 'CM',
    1000 => 'M', 2000 => 'MM', 3000 => 'MMM'
  }

  def to_roman
    to_s.reverse.chars.each_with_index.inject("") do |roman_numeral, (character, index)|
      ROMAN_NUMERALS[character.to_i * 10 ** index] + roman_numeral
    end
  end
end

我正在使用的测试位于

之下
require 'minitest/autorun'
require_relative 'roman'

class RomanTest < MiniTest::Unit::TestCase
  def test_1
    assert_equal 'I', 1.to_roman
  end

  def test_2
    assert_equal 'II', 2.to_roman
  end

  def test_3
    assert_equal 'III', 3.to_roman
  end

  def test_4
    assert_equal 'IV', 4.to_roman
  end

  def test_5
    assert_equal 'V', 5.to_roman
  end

  def test_6
    assert_equal 'VI', 6.to_roman
  end

  def test_9
    assert_equal 'IX', 9.to_roman
  end

  def test_27
    assert_equal 'XXVII', 27.to_roman
  end

  def test_48
    assert_equal 'XLVIII', 48.to_roman
  end

  def test_59
    assert_equal 'LIX', 59.to_roman
  end

  def test_93
    assert_equal 'XCIII', 93.to_roman
  end

  def test_141
    assert_equal 'CXLI', 141.to_roman
  end

  def test_163
    assert_equal 'CLXIII', 163.to_roman
  end

  def test_402
    assert_equal 'CDII', 402.to_roman
  end

  def test_575
    assert_equal 'DLXXV', 575.to_roman
  end

  def test_911
    assert_equal 'CMXI', 911.to_roman
  end

  def test_1024
    assert_equal 'MXXIV', 1024.to_roman
  end

  def test_3000
    assert_equal 'MMM', 3000.to_roman
  end
end

在一种情况下查看规格如何失败,而在另一种情况下则不然。我认为这些都是以同样的方式工作。

3 个答案:

答案 0 :(得分:3)

这一行是一个问题...

ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral

它将返回一个字符串,该字符串是正确的ROMAN_NUMERALS键加roman_numeral的值,这是您想要的,但它也会更改ROMAN_NUMERALS哈希中的值! shovel运算符<<更改运算符左侧的字符串(我们称之为变异运算符)。

因此,如果你测试1001,单位1将返回“I”(那很好),那么零将返回一个空字符串BUT会将零值更改为“I”...第二个零将返回“I”(不正确)并将零值更改为“II”。千位中的1将返回“M”,但随后将哈希值更改为“MII”。

答案 1 :(得分:2)

当执行第ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral行时,您将使用其值加character.to_i * 10 ** index替换与键roman_numeral对应的值。

答案 2 :(得分:2)

现在您的问题已经得到解答,我想建议一种替代方法,也可以采用不同的方式来执行测试。这需要Ruby v1.9 +,因此我们可以依赖于散列键的顺序。

<强>代码

首先,颠倒哈希元素的顺序。

RNR = Hash[ROMAN_NUMERALS.to_a.reverse]
  #=> {3000=>"MMM", 2000=>"MM", 1000=>"M",..., 2=>"II", 1=>"I", 0=>""}  

然后:

class Integer
  def to_roman
    num = self
    roman = ""
    while num > 0
      i,r = RNR.find { |i,r| i <= num }
      roman << r
      num -= i
    end
    roman
  end
end

测试目标

我们需要测试大量的整数值,并确保我们正在测试每个整数与正确的罗马数字等价物。通过创建将罗马数字转换为整数的方法,可以满足这两个目标:

RNRI = RNR.invert
  #=> {"MMM"=>3000, "MM"=>2000, "M"=>1000,..., "II"=>2, "I"=>1, ""=>0} 

class String
  def roman_to_integer
    num = 0
    roman = self
    while roman.size > 0
      r, i = RNRI.find { |r,m| roman =~ /^#{r}/ }
      num += i
      roman = roman[r.size..-1]
    end
    num
  end  
end

<强>实施例

现在让我们调用Integer#to_romanString#roman_to_integer来获取各种整数值:

def check_one(i)
  roman = i.to_roman
  puts "#{i}.to_roman = #{roman}, #{roman}.roman_to_integer = " +
    #{roman.roman_to_integer}"
end

check_one(402)  # 'CDII'
  # 402.to_roman = CDII, CDII.roman_to_integer = 402
check_one(575)  # 'DLXXV'
  # 575.to_roman = DLXXV, DLXXV.roman_to_integer = 575
check_one(911)  # 'CMXI'
  # 911.to_roman = CMXI, CMXI.roman_to_integer = 911
check_one(1024) # 'MXXIV'
  # 1024.to_roman = MXXIV, MXXIV.roman_to_integer = 1024
check_one(3000) # 'MMM'
  # 3000.to_roman = MMM, MMM.roman_to_integer = 3000

<强>测试

所以现在在你的测试中你可以使用:

def test_all(n)
  (1..n).each { |i| test_one(i) }
end

def test_one(i)
  roman = i.to_roman
  assert_equal(i, roman.roman_to_integer, "#{i}.to_roman=#{roman}, " +
    "#{roman}.roman_to_integer = #{roman.roman_to_integer}")
end