是否可以保持非字母符号完好无损?

Is it possible to keep the non letter symbols intact?

我是 Ruby 初学者,我正在开发一个密码程序。 它接受一个短语,将字符串转换为数字,用给定值递增数字,然后再次将它们转换为字符串。 我想知道如何保持非字母符号不变。例如“!”或 space。 我写的代码如下:

def caesar_cypher ( phrase, number=0)
    letters = phrase.downcase.split(//)
    
    letters_to_numbers= letters.map { |idx|  idx.ord }
    
    incrementor = letters_to_numbers.map { |idx| idx+number}
    
    numbers_to_letters = incrementor.map { |idx| idx.chr}.join.capitalize
    p numbers_to_letters
    #binding.pry
end

caesar_cypher("Hello world!", 4)
caesar_cypher("What a string!", 6)

使用 Array#rotate 和 Hash#fetch 的解决方案

是的,您可以不加修改地传递字符,但为此您需要定义什么是“字母”以及您希望在#map 块中的编码中包含或排除的内容。这是处理执行这些操作的问题的一种略有不同的方法,但也更短并且增加了一些额外的灵活性。

  1. 创建一个包含英文字母表中所有大写和小写字符的数组,并使用 Array#rotate 的倒置哈希值为每个字符分配一个替换值,其中旋转值是您的可重现密码密钥。
  2. 当您没有加密值时发出警告,因为旋转是 key % 26 == 0,但无论如何都允许它。这有助于测试。否则,如果您不想允许明文结果,您可以简单地引发异常,或者为 key.
  3. 设置默认值
  4. 不要大写你的句子。这限制了您的随机性,并阻止您为大写字母设置单独的值。
  5. 使用带有 Hash#fetch 的默认值允许您 return 任何不在您的 Hash 中的字符而不对其进行编码,因此 UTF-8 或标点符号将简单地按原样传递.
  6. 空格不是散列中定义的编码的一部分,因此您可以使用 String#join 而无需特殊对待它们。

使用 Ruby 3.0.2:

def caesar_cypher phrase, key
  warn "no encoding when key=#{key}" if (key % 26).zero?

  letters = [*(?A..?Z), *(?a..?z)]
  encoding = letters.rotate(key).zip(letters).to_h.invert
  phrase.chars.map { encoding.fetch _1, _1 }.join
end

您可以通过以下一些示例验证这是否为您提供了可重复的输出:

# verify your mapping with key=0,
# which returns the phrase as-is
caesar_cypher "foo bar", 0
#=> "foo bar"

caesar_cypher "foo bar", 5
#=> "ktt gfw"

caesar_cypher "Et tu, Brute?", 43
#=> "vk kl, silkV?"

# use any other rotation value you like;
# you aren't limited to just 0..51
caesar_cypher "Veni, vidi, vici", 152
#=> "Raje, reZe, reYe"

# UTF-8 and punctuation (actually, anything
# not /[A-Za-z]/) will simply pass through
# unencoded since it's not defined in the
# +encoding+ Hash
caesar_cypher "î.ô.ú.", 10
#=> "î.ô.ú."

编号参数的语法说明

上面的代码应该适用于最新的 Ruby 版本,但在早于 2.7 的版本上,您可能需要将块中的 _1 变量替换为类似以下内容的内容:

  phrase.chars.map { |char| encoding.fetch(char, char) }.join

而不是依赖编号的位置参数。我想不出还有什么可以阻止此代码在任何未过期的 Ruby 版本上 运行,但是如果您发现一些具体的内容,请添加评论。

A_ORD = 'A'.ord
def caesar_cypher(str, offset)
  h = ('A'..'Z').each_with_object(Hash.new(&:last)) do |ch,h|
    h[ch] = (A_ORD + (ch.ord - A_ORD + offset) % 26).chr
    h[ch.downcase] = (h[ch].ord + 32).chr
  end
  str.gsub(/./, h)
end 

试试吧。

caesar_cypher("Hello world!", 4)
  #=> "Lipps asvph!"
caesar_cypher("What a string!", 6)
  #=> "Cngz g yzxotm!"

在执行第一个示例时,变量 h 持有的散列值等于

{"A"=>"E", "a"=>"e", "B"=>"F", "b"=>"f", "C"=>"G", "c"=>"g", "D"=>"H",
 "d"=>"h", "E"=>"I", "e"=>"i", "F"=>"J", "f"=>"j", "G"=>"K", "g"=>"k",
 "H"=>"L", "h"=>"l", "I"=>"M", "i"=>"m", "J"=>"N", "j"=>"n", "K"=>"O",
 "k"=>"o", "L"=>"P", "l"=>"p", "M"=>"Q", "m"=>"q", "N"=>"R", "n"=>"r",
 "O"=>"S", "o"=>"s", "P"=>"T", "p"=>"t", "Q"=>"U", "q"=>"u", "R"=>"V",
 "r"=>"v", "S"=>"W", "s"=>"w", "T"=>"X", "t"=>"x", "U"=>"Y", "u"=>"y",
 "V"=>"Z", "v"=>"z", "W"=>"A", "w"=>"a", "X"=>"B", "x"=>"b", "Y"=>"C",
 "y"=>"c", "Z"=>"D", "z"=>"d"}

片段

Hash.new(&:last)

如果与

相同
Hash.new { |h,k| k }

其中块变量 h 是正在创建的(初始为空)散列,k 是一个键。如果定义了哈希

hash = Hash.new { |h,k| k }

then(可能在添加键值对之后)如果hash没有键khash[k]returnsk(即, 字符 k 保持不变).

参见 Hash::new 的形式,它采用块但没有参数。


我们可以轻松创建解密方法。

def caesar_decrypt(str, offset)
  caesar_cypher(str, 26-offset)
end
offset = 4
s = caesar_cypher("Hello world!", offset)
  #=> "Lipps asvph!"
caesar_decrypt(s, offset)
  #=> "Hello world!"
offset = 24
s = caesar_cypher("Hello world!", offset)
  #=> Fcjjm umpjb!
caesar_decrypt(s, offset)
  #=> "Hello world!"