Как сохранить регистр символов при использовании шифра Цезаря

У меня есть скрипт Caesar Cipher на Ruby, который работает, но он возвращает строку как все буквы верхнего регистра вместо сохранения регистров исходной строки.

Я мог бы использовать capitalize, чтобы он выглядел достаточно хорошо, но мне нужен более конкретный способ сохранения дел.

Вот сценарий:

BASE_ORD = 'A'.ord

def caesar_cipher(phrase, key)
  cipher = phrase.gsub(/[a-z]/i) do |c|
    orig_pos = c.upcase.ord - BASE_ORD
    new_pos = (orig_pos + key) % 26
    (new_pos + BASE_ORD).chr
  end
  puts cipher
end

caesar_cipher("What a string!", 5) 

Любая помощь или понимание будут оценены.


person Community    schedule 14.12.2015    source источник
comment
Ваше регулярное выражение нечувствительно к регистру, вы, вероятно, захотите зафиксировать все фактические случаи: phrase.gsub(/\w/)   -  person Brennan    schedule 15.12.2015
comment
@Brennan: \w также включает в себя некоторые вещи, которые не являются буквами, поэтому [a-zA-Z] было бы лучше. Проблема на самом деле upcase. И в любом случае с tr это сделать проще, чем с gsub.   -  person Amadan    schedule 15.12.2015
comment
Ruby проще, чем UTF-8/Unicode... Вот моя ссылка на github.   -  person Charles    schedule 15.12.2015


Ответы (2)


Самое простое решение, учитывая ваш существующий код, — проверить, является ли символ прописным или строчным, и соответственно установить base_ord. Поскольку строчные буквы идут после прописных букв в UTF-8 (как в ASCII), мы можем просто проверить letter >= 'a', например:

base_ord = (letter >= 'a' ? 'a' : 'A').ord

Вот весь метод с этим изменением (вам больше не нужна константа BASE_ORD):

def caesar_cipher(phrase, key)
  phrase.gsub(/[a-z]/i) do |letter|
    base_ord = (letter >= 'a' ? 'a' : 'A').ord
    orig_pos = letter.ord - base_ord
    new_pos = (orig_pos + key) % 26
    (new_pos + base_ord).chr
  end
end

puts caesar_cipher("What a string!", 5) # => Bmfy f xywnsl!

Редактировать

Амадан хорошо отмечает использование String#tr. Вот несколько более краткая реализация:

ALPHABET = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
# Or if you want to be fancy: ALPHABET = (?a..?z).flat_map {|c| [ c, c.upcase ] }.join

def caesar_cipher(phrase, key)
  to_alphabet = ALPHABET.dup
  to_alphabet << to_alphabet.slice!(0, key * 2)
  phrase.tr(ALPHABET, to_alphabet)
end

puts caesar_cipher("What a string!", 5) # => Bmfy f xywnsl!
person Jordan Running    schedule 14.12.2015
comment
Спасибо! использование знака «больше» или «равно» сработало отлично! Однако мне придется попробовать метод String#tr, который выглядит немного проще! - person ; 17.12.2015

Как сказано в комментариях, tr проще использовать для Caesar Cypher (после того, как вы подготовите два алфавита), а также он должен быть намного быстрее:

class CaesarCypher
    def initialize(key, alphabet=nil)
        @from_alphabet = alphabet || (?a..?z).to_a.join
        @to_alphabet = @from_alphabet[key..-1] + @from_alphabet[0...key]
        @from_alphabet += @from_alphabet.upcase
        @to_alphabet += @to_alphabet.upcase
    end
    def encode(str)
        str.tr(@from_alphabet, @to_alphabet)
    end
    def encode!(str)
        str.tr!(@from_alphabet, @to_alphabet)
    end
    def decode(str)
        str.tr(@to_alphabet, @from_alphabet)
    end
    def decode(str)
        str.tr!(@to_alphabet, @from_alphabet)
    end
end

cc = CaesarCypher.new(1)
puts cc.encode("Caesar, huh?")
puts cc.decode("Dbftbs, ivi?")
person Amadan    schedule 14.12.2015