如何拆分字符串、迭代并输出连接版本

3
我正在编写一种加密方法。我正在为凯撒密码编写代码。下面是我编写的代码:
def caesar(string, shift = 0)
  alphabet = ("a".."z").to_a
  letters = string.split("")
  blank = []
  letters.map do |letter|
    blank << alphabet[(alphabet.index(letter) + shift) % alphabet.length]   
  end       
  puts blank.join
end

当我传递一个由多个单词组成的字符串时,会出现这个错误:
block in `caesar_cipher': undefined method `+' for nil:NilClass (NoMethodError)

我了解如果我创建一个类,tr 可以解决我的问题。我有一个独立的方法,可以处理 caesar_cipher 的每个角度,但无法处理句子。我无法将单词拆分、迭代,然后再合并。任何输入都将帮助我避免这种情况。

你的问题不够清晰。这段代码与caesar_cipher有什么关系? - sawa
4个回答

2

通过借鉴回复意见,我已经成功地将代码进行了调整,使其可以用于句子,而不仅仅是单词:

def caesar(string, shift=0)
alphabet = ("a".."z").to_a
    blank = string.each_char.map do |letter|
    alphabet.include?(letter) ? alphabet[(alphabet.index(letter) + shift) % alphabet.length] : letter
    end
puts blank.join
end

1
错误是由以下原因引起的:
(alphabet.index(letter) + shift)

如果有多个单词,每个空格都是letter = " "。现在你正在执行alphabet.index(letter)。由于数组alphabet没有" "元素,它返回nil,随后进行加法运算,导致出现适当的错误:

未定义方法 `+' for nil:NilClass (NoMethodError)

一旦你理解了错误的来源,解决方案取决于你的算法。


1
现在我完全明白错误的根源是因为当letter =“ ”时,alphabet.index(letter)变成nil,然后将nil添加到shift中并创建错误,正如@Jordan上面解释的那样,一个简单的if else语句就能覆盖空格。 - kparekh01

1
不要费心拆分/连接单词。只需跳过不在您字母表中的加密字符:
blank = letters.map do |letter|
  if alphabet.include?(letter)
    alphabet[(alphabet.index(letter) + shift) % alphabet.length]
  else
    letter
  end
end

您会注意到上面的代码没有执行blank << ...。这在使用each时可以工作,但在使用map时不行。 map返回一个新数组,其中每个项对应于原始数组上的每个项,因此此代码只是将结果数组分配给blank(这意味着没有必要首先执行blank = [])。
附注:不要使用letters = string.split(""),而要使用letters = string.chars,或者更好的方法是摆脱letters,并执行string.each_char.map ...

感谢您提供简单而又强大的解释。if else 代码块可以覆盖空格,最后通过 blank.join 将它们重新组合在一起。我还使用了 string.each_char.map 对代码进行了微调,这样就不需要 letters 变量了。现在我完全明白为什么我的代码只适用于一个单词了。 - kparekh01
1
ps-我已经为你的答案点赞,但是你必须等到我有15个声望点才能看到它。再次感谢! - kparekh01

1
作为您的问题已经被确认,让我建议另一种加密方式,这将说明使用各种具有广泛应用的Ruby方法。我将假设要加密的字符串不包含大写字母,只有字母需要移位。 代码
def caesar(str, shift = 0)
  arr = [*'a'..'z']
  mapping = arr.zip(arr.rotate(shift)).to_h
  mapping.default_proc = proc { |h,k| h[k] = k }
  str.gsub(/./, mapping)
end

例子

caesar "the die is cast.", 3
  #=> "wkh glh lv fdvw."

解释

首先,对于 shift = 3

arr = [*'a'..'z']
  #=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 
  #    "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] 
mapping = arr.zip(arr.rotate(shift)).to_h
  #=> {"a"=>"d", "b"=>"e", "c"=>"f", "d"=>"g", "e"=>"h", "f"=>"i", "g"=>"j",
  #    "h"=>"k", "i"=>"l", "j"=>"m", "k"=>"n", "l"=>"o", "m"=>"p", "n"=>"q",
  #    "o"=>"r", "p"=>"s", "q"=>"t", "r"=>"u", "s"=>"v", "t"=>"w", "u"=>"x",
  #    "v"=>"y", "w"=>"z", "x"=>"a", "y"=>"b", "z"=>"c"}

接下来,我们使用Hash#default_proc=向此哈希表添加一个默认过程:

mapping.default_proc = proc { |h,k| h[k] = k }
  # => #<Proc:0x007f8ec8b132b8@(irb):1241> 

这会导致mapping[k]mapping没有键k时返回k。 (仅此而已。) 例如,
mapping['a'] #=> 'd'
mapping['4'] #=> '4'
mapping['$'] #=> '$'
mapping[' '] #=> ' '

我假设未加密的消息是凯撒最喜欢的表达之一:

str = "the die is cast."

我们使用 String#gsub 的形式来获取替换值。由于我们希望考虑替换每个字符,因此需要在正则表达式 /./ 上进行匹配:
str.gsub(/./, mapping)
  #=> "wkh glh lv fdvw."

如果我们没有将默认的proc附加到哈希中,我们就会得到"wkhglhlvfdvw"

感谢您提供了一种替代编写加密代码的方法,解释得非常好。但是,我正在寻找对我已经编写的代码进行微调的方法,以帮助我理解我的错误之处。问题在于,我还在学习这门语言,.zip、.rotate和.default_proc都是我尚未完全掌握的方法。这是我的问题,因为我没有在问题中指明这一点。话虽如此,我已将您的代码保存在一个.rb文件中作为备选方案,当我遇到这些方法时,它将成为一个很好的参考点。 - kparekh01
ps-我已经为你的答案点赞,但你必须等到我有15个声望点才能显示。再次感谢! - kparekh01
仅作说明,我提供了我的答案,只是因为我认为@shivram已经处理了你的问题。我觉得这对一些读者以及你自己可能会有所帮助。我不声称这是最好的解决方案。(你的更好!)我只是想说明这种方法。我不知道答案是否超出了你对Ruby的了解,但我想如果是的话,你可以稍后再回来看看。在你提到的方法中,“zip”经常被使用-你很快就会用到它。 - Cary Swoveland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接