How to implement Caesar Cipher in Ruby? - ruby

I'm learning Ruby on theOdinProject and I need to build the Caeser Cipher. Here's my code:
def caesar_cipher plaintext, factor
codepoints_array = []
ciphertext = ""
plaintext.split('').each do |letter|
if letter =~ /[^a-zA-Z]/
codepoints_array << letter.bytes.join('').to_i
else
codepoints_array << letter.bytes.join('').to_i + factor
end
end
ciphertext = codepoints_array.pack 'C*'
puts ciphertext
end
caesar_cipher("What a string!", 5)
I bet it's not really "elegant" but the main issue here is that the output should be "Bmfy f xywnsl!" but what do I have is "\mfy f xywnsl!". I've been struggling with this task for a couple of days now, but I still have no idea how to "chain" the alphabet so 'z' becomes 'a' with the factor == 1.
I could check the finished tasks of the other people on theOdinProject but their code usually different/more professional and I tried to get a hint, not the final solution. I'll be really thankful if someone could hint me how to resolve this. Thank you in advance.

Hints
Your code would almost work fine if the ASCII table had only 26 characters.
But W is not w, and after z comes {, not a.
So you first need to apply downcase to your letters, offset the bytecode so that a is 0, and do every calculation modulo 26.
Modified version
def caesar_cipher plaintext, factor
codepoints_array = []
ciphertext = ""
a_codepoint = 'a'.ord
plaintext.split('').each do |letter|
if letter =~ /[^a-zA-Z]/
codepoints_array << letter.bytes.join('').to_i
else
shifted_codepoint = letter.downcase.bytes.join('').to_i + factor
codepoints_array << (shifted_codepoint - a_codepoint) % 26 + a_codepoint
end
end
ciphertext = codepoints_array.pack 'C*'
ciphertext
end
puts caesar_cipher("What a string!", 5) #=> "bmfy f xywnsl!"
Another solution
I wrote a small Ruby script for Vigenere chiper a while ago. Caesar cipher is just a variant of it, with the same factor for every character :
class Integer
# 0 => 'a', 1 => 'b', ..., 25 => 'z', 26 => 'a'
def to_letter
('a'.ord + self % 26).chr
end
end
class String
# 'A' => '0', 'a' => 0, ..., 'z' => 25
def to_code
self.downcase.ord - 'a'.ord
end
end
def caesar_cipher(string, factor)
short_string = string.delete('^A-Za-z')
short_string.each_char.map do |char|
(char.to_code + factor).to_letter
end.join
end
puts caesar_cipher("What a string!", 5) #=> "bmfyfxywnsl"
puts caesar_cipher("bmfyfxywnsl", -5) #=> "whatastring"
With ciphers, it is recommended to remove any punctuation sign or whitespace, because they make it much easier to decode the string with statistical analysis.
Caesar cipher is very weak anyway.

Related

Codewars: "Return or rotate": why isn't my attempted solution working?

These were the instructions given on Codewars (https://www.codewars.com/kata/56b5afb4ed1f6d5fb0000991/train/ruby):
The input is a string str of digits. Cut the string into chunks (a chunk here is a substring of the initial string) of size sz (ignore the last chunk if its size is less than sz).
If a chunk represents an integer such as the sum of the cubes of its digits is divisible by 2, reverse that chunk; otherwise rotate it to the left by one position. Put together these modified chunks and return the result as a string.
If
sz is <= 0 or if str is empty return ""
sz is greater (>) than the length of str it is impossible to take a chunk of size sz hence return "".
Examples:
revrot("123456987654", 6) --> "234561876549"
revrot("123456987653", 6) --> "234561356789"
revrot("66443875", 4) --> "44668753"
revrot("66443875", 8) --> "64438756"
revrot("664438769", 8) --> "67834466"
revrot("123456779", 8) --> "23456771"
revrot("", 8) --> ""
revrot("123456779", 0) --> ""
revrot("563000655734469485", 4) --> "0365065073456944"
This was my code (in Ruby):
def revrot(str, sz)
# your code
if sz > str.length || str.empty? || sz <= 0
""
else
arr = []
while str.length >= sz
arr << str.slice!(0,sz)
end
arr.map! do |chunk|
if chunk.to_i.digits.reduce(0) {|s, n| s + n**3} % 2 == 0
chunk.reverse
else
chunk.chars.rotate.join
end
end
arr.join
end
end
It passed 13/14 test and the error I got back was as follows:
STDERR/runner/frameworks/ruby/cw-2.rb:38:in `expect': Expected: "", instead got: "095131824330999134303813797692546166281332005837243199648332767146500044" (Test::Error)
from /runner/frameworks/ruby/cw-2.rb:115:in `assert_equals'
from main.rb:26:in `testing'
from main.rb:84:in `random_tests'
from main.rb:89:in `<main>'
I'm not sure what I did wrong, I have been trying to find what it could be for over an hour. Could you help me?
I will let someone else identify the problem with you code. I merely wish to show how a solution can be speeded up. (I will not include code to deal with edge cases, such as the string being empty.)
You can make use of two observations:
the cube of an integer is odd if and only if the integer is odd; and
the sum of collection of integers is odd if and only if the number of odd integers is odd.
We therefore can write
def sum_of_cube_odd?(str)
str.each_char.count { |c| c.to_i.odd? }.odd?
end
Consider groups of 4 digits in the last example, "563000655734469485".
sum_of_cube_odd? "5630" #=> false (so reverse -> "0365")
sum_of_cube_odd? "0065" #=> true (so rotate -> "0650")
sum_of_cube_odd? "5734" #=> true (so rotate -> "7345")
sum_of_cube_odd? "4694" #=> true (so rotate -> "6944")
so we are to return "0365065073456944".
Let's create another helper.
def rotate_chars_left(str)
str[1..-1] << s[0]
end
rotate_chars_left "0065" #=> "0650"
rotate_chars_left "5734" #=> "7345"
rotate_chars_left "4694" #=> "6944"
We can now write the main method.
def revrot(str, sz)
str.gsub(/.{,#{sz}}/) do |s|
if s.size < sz
''
elsif sum_of_cube_odd?(s)
rotate_chars_left(s)
else
s.reverse
end
end
end
revrot("123456987654", 6) #=> "234561876549"
revrot("123456987653", 6) #=> "234561356789"
revrot("66443875", 4) #=> "44668753"
revrot("66443875", 8) #=> "64438756"
revrot("664438769", 8) #=> "67834466"
revrot("123456779", 8) #=> "23456771"
revrot("563000655734469485", 4) #=> "0365065073456944"
It might be slightly faster to write
require 'set'
ODD_DIGITS = ['1', '3', '5', '7', '9'].to_set
#=> #<Set: {"1", "3", "5", "7", "9"}>
def sum_of_cube_odd?(str)
str.each_char.count { |c| ODD_DIGITS.include?(c) }.odd?
end

How to write nested if/then statements in Ruby

I'm supposed to
define a method, three_digit_format(n), that accepts an integer, n, as an argument. Assume that n < 1000. Your method should return a string version of n, but with leading zeros such that the string is always 3 characters long.
I have been tinkering with versions of the below code, but I always get errors. Can anyone advise?
def three_digit_format(n)
stringed = n.to_s
stringed.size
if stringed.size > 2
return stringed
end
elsif stringed > 1
return "0" + stringed
end
else
return "00" + stringed
end
end
puts three_digit_format(9)
rjust
You could just use rjust:
n.to_s.rjust(3, '0')
If integer is greater than the length of str, returns a new String of
length integer with str right justified and padded with padstr;
otherwise, returns str.
Your code
Problem
If you let your text editor indents your code, you can notice there's something wrong:
def three_digit_format(n)
stringed = n.to_s
stringed.size
if stringed.size > 2
return stringed
end
elsif stringed > 1 # <- elsif shouldn't be here
return "0" + stringed
end
else
return "00" + stringed
end
end
puts three_digit_format(9)
Solution
if, elsif and else belong to the same expression : there should only be one end at the end of the expression, not for each statement.
def three_digit_format(n)
stringed = n.to_s
if stringed.size > 2
return stringed
elsif stringed.size > 1
return "0" + stringed
else
return "00" + stringed
end
end
puts three_digit_format(9)
# 009
This function, as some have pointed out, is entirely pointless since there's several built-in ways of doing this. Here's the most concise:
def three_digit_format(n)
'%03d' % n
end
Exercises that force you to re-invent tools just drive me up the wall. That's not what programming is about. Learning to be an effective programmer means knowing when you have a tool at hand that can do the job, when you need to use several tools in conjunction, or when you have no choice but to make your own tool. Too many programmers jump immediately to writing their own tools and overlook more elegant solutions.
If you're committed to that sort of approach due to academic constraints, why not this?
def three_digit_format(n)
v = n.to_s
while (v.length < 3)
v = '0' + v
end
v
end
Or something like this?
def three_digit_format(n)
(n + 1000).to_s[1,3]
end
Where in that case values of the form 0-999 will be rendered as "1000"-"1999" and you can just trim off the last three characters.
Since these exercises are often absurd, why not take this to the limit of absurdity?
def three_digit_format(n)
loop do
v = Array.new(3) { (rand(10) + '0'.ord).chr }.join('')
return v if (v.to_i == n)
end
end
If you're teaching things about if statements and how to append elsif clauses, it makes sense to present those in a meaningful context, not something contrived like this. For example:
if (customer.exists? and !customer.on_fire?)
puts('Welcome back!')
elsif (!customer.exists?)
puts('You look new here, welcome!')
else
puts('I smell burning.')
end
There's so many ways a chain of if statements is unavoidable, it's how business logic ends up being implemented. Using them in inappropriate situations is how code ends up ugly and Rubocop or Code Climate give you a failing grade.
As others have pointed out, rjust and applying a format '%03d' % n are built in ways to do it.
But if you have to stick to what you've learned so far, I wonder if you've been introduced to the case statement?
def three_digit_format(n)
case n
when 0..9
return "00#{n}"
when 10..99
return "0#{n}"
when 100..999
return "#{n}"
end
end
I think it's cleaner than successive if statements.
Here's my spin on it:
def three_digit_format(n)
str = n.to_s
str_len = str.length
retval = if str_len > 2
str
elsif str_len > 1
'0' + str
else
'00' + str
end
retval
end
three_digit_format(1) # => "001"
three_digit_format(12) # => "012"
three_digit_format(123) # => "123"
Which can be reduced to:
def three_digit_format(n)
str = n.to_s
str_len = str.length
if str_len > 2
str
elsif str_len > 1
'0' + str
else
'00' + str
end
end
The way it should be done is by taking advantage of String formats:
'%03d' % 1 # => "001"
'%03d' % 12 # => "012"
'%03d' % 123 # => "123"

How do I use a hash to modify the values of an Array?

I am building a base converter. Here is my code so far:
def num_to_s(num, base)
remainders = [num]
while base <= num
num /= base #divide initial value of num
remainders << num #shovel results into array to map over for remainders
end
return remainders.map{|i| result = i % base}.reverse.to_s #map for remainders and shovel to new array
puts num_to_s(40002, 16)
end
Now it's time to account for bases over 10 where letters replace numbers. The instructions (of the exercise) suggest using a hash. Here is my hash:
conversion = {10 => 'A', 11 => 'B', 12 => 'C', 13 => 'D', 14 => 'E', 15 => 'F',}
The problem is now, how do I incorporate it so that it modifies the array? I have tried:
return remainders.map{|i| result = i % base}.map{|i| [i, i]}.flatten.merge(conversion).reverse.to_s
In an attempt to convert the 'remainders' array into a hash and merge them so the values in 'conversion' override the ones in 'remainders', but I get an 'odd list for Hash' error. After some research it seems to be due to the version of Ruby (1.8.7) I am running, and was unable to update. I also tried converting the array into a hash outside of the return:
Hashes = Hash[remainders.each {|i, i| [i, i]}].merge(conversion)
and I get an 'dynamic constant assignment' error. I have tried a bunch of different ways to do this... Can a hash even be used to modify an array? I was also thinking maybe I could accomplish this by using a conditional statement within an enumerator (each? map?) but haven't been able to make that work. CAN one put a conditional inside an enumerator?
Yes, you could use a hash:
def digit_hash(base)
digit = {}
(0...[10,base].min).each { |i| digit.update({ i=>i.to_s }) }
if base > 10
s = ('A'.ord-1).chr
(10...base).each { |i| digit.update({ i=>s=s.next }) }
end
digit
end
digit_hash(40)
#=> { 0=>"0", 1=>"1", 2=>"2", 3=>"3", 4=>"4",
# 5=>"5", 6=>"6", 7=>"7", 8=>"8", 9=>"9",
# 10=>"A", 11=>"B", 12=>"C", ..., 34=>"Y", 35=>"Z",
# 36=>"AA", 37=>"AB", 38=>"AC", 39=>"AD" }
There is a problem in displaying digits after 'Z'. Suppose, for example, the base were 65. Then one would not know if "ABC" was 10-11-12, 37-12 or 10-64. That's detail we needn't worry about.
For variety, I've done the base conversion from high to low, as one might do with paper and pencil for base 10:
def num_to_s(num, base)
digit = digit_hash(base)
str = ''
fac = base**((0..Float::INFINITY).find { |i| base**i > num } - 1)
until fac.zero?
d = num/fac
str << digit[d]
num -= d*fac
fac /= base
end
str
end
Let's try it:
num_to_s(134562,10) #=> "134562"
num_to_s(134562, 2) #=> "100000110110100010"
num_to_s(134562, 8) #=> "406642"
num_to_s(134562,16) #=> "20DA2"
num_to_s(134562,36) #=> "2VTU"
Let's check the last one:
digit_inv = digit_hash(36).invert
digit_inv["2"] #=> 2
digit_inv["V"] #=> 31
digit_inv["T"] #=> 29
digit_inv["U"] #=> 30
So
36*36*36*digit_inv["2"] + 36*36*digit_inv["V"] +
36*digit_inv["T"] + digit_inv["U"]
#=> 36*36*36*2 + 36*36*31 + 36*29 + 30
#=> 134562
The expression:
(0..Float::INFINITY).find { |i| base**i > num }
computes the smallest integer i such that base**i > num. Suppose, for example,
base = 10
num = 12345
then i is found to equal 5 (10**5 = 100_000). We then raise base to this number less one to get the initial factor:
fac = base**(5-1) #=> 10000
Then the first (base-10) digit is
d = num/fac #=> 1
the remainder is
num -= d*fac #=> 12345 - 1*10000 => 2345
and the factor for the next digit is:
fac /= base #=> 10000/10 => 1000
I made a couple of changes from my initial answer to make it 1.87-friedly (I removed Enumerator#with_object and Integer#times), but I haven't tested with 1.8.7, as I don't have that version installed. Let me know if there are any problems.
Apart from question, you can use Fixnum#to_s(base) to convert base.
255.to_s(16) # 'ff'
I would do a
def get_symbol_in_base(blah)
if blah < 10
return blah
else
return (blah - 10 + 65).chr
end
end
and after that do something like:
remainders << get_symbol_in_base(num)
return remainders.reverse.to_s

How can I convert a human-readable number to a computer-readable number in Ruby?

I'm working in Ruby with an array that contains a series of numbers in human-readable format (e.g., 2.5B, 1.27M, 600,000, where "B" stands for billion, "M" stands for million). I'm trying to convert all elements of the array to the same format.
Here is the code I've written:
array.each do |elem|
if elem.include? 'B'
elem.slice! "B"
elem = elem.to_f
elem = (elem * 1000000000)
else if elem.include? 'M'
elem.slice! "M"
elem = elem.to_f
elem = (elem * 1000000)
end
end
When I inspect the elements of the array using puts(array), however, the numbers appear with the "B" and "M" sliced off but the multiplication conversion does not appear to have been applied (e.g., the numbers now read 2.5, 1.27, 600,000, instead of 2500000000, 1270000, 600,000).
What am I doing wrong?
First thing to note is that else if in ruby is elsif. See http://www.tutorialspoint.com/ruby/ruby_if_else.htm
Here is a working function for you to try out:
def convert_array_items_from_human_to_integers(array)
array.each_with_index do |elem,i|
if elem.include? 'B'
elem.slice! "B"
elem = elem.to_f
elem = (elem * 1000000000)
elsif elem.include? 'M'
elem.slice! "M"
elem = elem.to_f
elem = (elem * 1000000)
end
array[i] = elem
end
return array
end
Calling convert_array_items_from_human_to_integers(["2.5B", "1.2M"])
returns [2500000000.0, 1200000.0]
Another variation:
array = ['2.5B', '1.27M', '$600000']
p array.each_with_object([]) { |i, a|
i = i.gsub('$', '')
a << if i.include? 'B'
i.to_f * 1E9
elsif i.include? 'M'
i.to_f * 1E6
else
i.to_f
end
}
#=> [2500000000.0, 1270000.0, 600000.0]
Try this:
array.map do |elem|
elem = elem.gsub('$','')
if elem.include? 'B'
elem.to_f * 1000000000
elsif elem.include? 'M'
elem.to_f * 1000000
else
elem.to_f
end
end
This uses map instead of each to return a new array. Your attempt assigns copies of the array elements, leaving the original array in place (except for the slice!, which modifies in place). You can dispense with the slicing in the first place, since to_f will simply ignore any non-numeric characters.
EDIT:
If you have leading characters such as $2.5B, as your question title indicates (but not your example), you'll need to strip those explicitly. But your sample code doesn't handle those either, so I assume that's not an issue.
Expanding a bit on pjs' answer:
array.each do |elem|
elem is a local variable pointing to each array element, one at a time. When you do this:
elem.slice! "B"
you are sending a message to that array element telling it to slice the B. And you're seeing that in the end result. But when you do this:
elem = elem.to_f
now you've reassigned your local variable elem to something completely new. You haven't reassigned what's in the array, just what elem is.
Here's how I'd go about it:
ARY = %w[2.5B 1.27M 600,000]
def clean_number(s)
s.gsub(/[^\d.]+/, '')
end
ARY.map{ |v|
case v
when /b$/i
clean_number(v).to_f * 1_000_000_000
when /m$/i
clean_number(v).to_f * 1_000_000
else
clean_number(v).to_f
end
}
# => [2500000000.0, 1270000.0, 600000.0]
The guts of the code are in the case statement. A simple check for the multiplier allows me to strip the undesired characters and multiply by the right value.
Normally we could use to_f to find the floating-point number to be multiplied for strings like '1.2', but it breaks down for things like '$1.2M' because of the "$". The same thing is true for embedded commas marking thousands:
'$1.2M'.to_f # => 0.0
'1.2M'.to_f # => 1.2
'6,000'.to_f # => 6.0
'6000'.to_f # => 6000.0
To fix the problem for simple strings containing just the value, it's not necessary to do anything fancier than stripping undesirable characters using gsub(/[^\d.]+/, ''):
'$1.2M'.gsub(/[^\d.]+/, '') # => "1.2"
'1.2M'.gsub(/[^\d.]+/, '') # => "1.2"
'6,000'.gsub(/[^\d.]+/, '') # => "6000"
'6000'.gsub(/[^\d.]+/, '') # => "6000"
[^\d.] means "anything NOT a digit or '.'.
Be careful how you convert your decimal values to integers. You could end up throwing away important precision:
'0.2M'.gsub(/[^\d.]+/, '').to_f * 1_000_000 # => 200000.0
'0.2M'.gsub(/[^\d.]+/, '').to_i * 1_000_000 # => 0
('0.2M'.gsub(/[^\d.]+/, '').to_f * 1_000_000).to_i # => 200000
Of course all this breaks down if your string is more complex than a simple number and multiplier. It's easy to break down a string and identify those sort of sub-strings, but that's a different question.
I would do it like this:
Code
T, M, B = 1_000, 1_000_000, 1_000_000_000
def convert(arr)
arr.map do |n|
m = n.gsub(/[^\d.TMB]/,'')
m.to_f * (m[-1][/[TMB]/] ? Object.const_get(m[-1]) : 1)
end
end
Example
arr = %w[$2.5B 1.27M 22.5T, 600,000]
convert(arr)
# => [2500000000.0, 1270000.0, 22500.0, 600000.0]
Explanation
The line
m = n.gsub(/[^\d.TMB]/,'')
# => ["2.5B", "1.27M", "22.5T", "600000"]
merely eliminates unwanted characters.
m.to_f * (m[-1][/[TMB]/] ? Object.const_get(m[-1]) : 1)
returns the product of the string converted to a float and a constant given by the last character of the string, if that character is T, M or B, else 1.
Actual implementation might be like this:
class A
T, M, B = 1_000, 1_000_000, 1_000_000_000
def doit(arr)
c = self.class.constants.map(&:to_s).join
arr.map do |n|
m = n.gsub(/[^\d.#{c}]/,'')
m.to_f * (m[-1][/[#{c}]/] ? self.class.const_get(m[-1]) : 1)
end
end
end
If we wished to change the reference for 1,000 from T to K and add T for trillion, we would need only change
T, M, B = 1_000, 1_000_000, 1_000_000_000
to
K, M, B, T = 1_000, 1_000_000, 1_000_000_000, 1_000_000_000_000

Insert Something Every X Number of Characters Without Regex

In this question, the asker requests a solution that would insert a space every x number of characters. The answers both involve using a regular expression. How might you achieve this without a regex?
Here's what I came up with, but it's a bit of a mouthful. Any more concise solutions?
string = "12345678123456781234567812345678"
new_string = string.each_char.map.with_index {|c,i| if (i+1) % 8 == 0; "#{c} "; else c; end}.join.strip
=> "12345678 12345678 12345678 12345678"
class String
def in_groups_of(n)
chars.each_slice(n).map(&:join).join(' ')
end
end
'12345678123456781234567812345678'.in_groups_of(8)
# => '12345678 12345678 12345678 12345678'
class Array
# This method is from
# The Poignant Guide to Ruby:
def /(n)
r = []
each_with_index do |x, i|
r << [] if i % n == 0
r.last << x
end
r
end
end
s = '1234567890'
n = 3
join_str = ' '
(s.split('') / n).map {|x| x.join('') }.join(join_str)
#=> "123 456 789 0"
This is slightly shorter but requires two lines:
new_string = ""
s.split(//).each_slice(8) { |a| new_string += a.join + " " }

Resources