I'm creating a caesar cipher in Ruby based on the code from Rosetta Code, but my code only prints the original word, rather than the modified word.
Here's my code:
class String
ALPHABET = ("A".."Z").to_a
def caesar_cipher(num)
self.tr(ALPHABET.join, ALPHABET.rotate(num).join)
end
end
word = gets.chomp.to_s
encypted = "#{word}".caesar_cipher(8)
decrypted = "#{word}".caesar_cipher(-8)
puts "Encrypt or Decrypt?"
choice = gets.chomp.to_s.downcase
if choice == "encrypt"
puts encypted
elsif choice == "decrypt"
puts decrypted
else
puts "Invalid option"
end
Any help would be greatly appreciated!
You don't necessarily need to declare a constant ALPHABET but you can if you want. The tr method copies a String object and then it will replace characters in the String with specified characters. As mentioned, you're only saying to check for capital letters and replace those but you're taking a word and making it downcase:
word = gets.chomp.to_s.downcase
You can modify your method like this:
class String
def caeser_cipher(num)
self.tr('a-z',("a".."z").to_a.rotate(num).join)
end
Now, you get the encrypted string (which you can always convert to upcase if you want):
"hello".caeser_cipher(8)
=> "pmttw"
Your ALPHABET array only contains uppercase letters, thus your corresponding method only accounts for uppercase letters. You need to update ALPHABET to contain lowercase letters and then your code will work as expected.
Related
My purpose is to accept a paragraph of text and find the specified phrase I want to REDACT, or replace.
I made a method that accepts an argument as a string of text. I break down that string into individual characters. Those characters are compared, and if they match, I replace those characters with *.
def search_redact(text)
str = ""
print "What is the word you would like to redact?"
redacted_name = gets.chomp
puts "Desired word to be REDACTED #{redacted_name}! "
#splits name to be redacted, and the text argument into char arrays
redact = redacted_name.split("")
words = text.split("")
#takes char arrays, two loops, compares each character, if they match it
#subs that character out for an asterisks
redact.each do |x|
if words.each do |y|
x == y
y.gsub!(x, '*') # sub redact char with astericks if matches words text
end # end loop for words y
end # end if statment
end # end loop for redact x
# this adds char array to a string so more readable
words.each do |z|
str += z
end
# prints it out so we can see, and returns it to method
print str
return str
end
# calling method with test case
search_redact("thisisapassword")
#current issues stands, needs to erase only if those STRING of characters are
# together and not just anywehre in the document
If I put in a phrase that shares characters with others parts of the text, for example, if I call:
search_redact("thisisapassword")
then it will replace that text too. When it accepts input from the user, I want to get rid of only the text password. But it then looks like this:
thi*i**********
Please help.
This is a classic windowing problem used to find a substring in a string. There are many ways to solve this, some that are much more efficient than others but I'm going to give you a simple one to look at that uses as much of your original code as possible:
def search_redact(text)
str = ""
print "What is the word you would like to redact?"
redacted_name = gets.chomp
puts "Desired word to be REDACTED #{redacted_name}! "
redacted_name = "password"
#splits name to be redacted, and the text argument into char arrays
redact = redacted_name.split("")
words = text.split("")
words.each.with_index do |letter, i|
# use windowing to look for exact matches
if words[i..redact.length + i] == redact
words[i..redact.length + i].each.with_index do |_, j|
# change the letter to an astrisk
words[i + j] = "*"
end
end
end
words.join
end
# calling method with test case
search_redact("thisisapassword")
The idea here is we're taking advantage of array == which allows us to say ["a", "b", "c"] == ["a", "b", "c"]. So now we just walk the input and ask does this sub array equal this other sub array. If they do match, we know we need to change the value so we loop through each element and replace it with a *.
I've been searching online. There are a lot of resources to explain the bigger concepts in the following code but not what the use and/or meaning is of the smaller details. What does the first two lines mean? (Also, I'm a beginner.)
word = 'word'
words =[]
puts 'Please type as many words per line then press the Enter Key.'
puts 'When you are finished press the Enter Key without typing anything.'
while word != ''
word = gets.chomp
words = words.push word
end
puts ''
puts 'Your original values:'
puts words
puts ''
puts 'Your values sorted:'
puts words.sort
puts ''
First:
word = 'word'
The part on the right initializes a new String object with the value "word". The part on the left and the equals sign make the variable word a reference to that string. Informally, this line assigns the string "word" to the variable word.
Next:
words = []
The part on the right initializes a new Array object. Since there's nothing between the square brackets, this creates an empty array. It's equivalent to Array.new. As above, the part on the left and the equals sign make the variable words a reference to the array. In other words, this line assigns the Array object to the variable words.
I am working on a caesar cipher which is a real simple cipher which shifts each letter in a message to the right in accordance with a given key. For example, with a key of 3, the message "hello" would become encrypted as "ifmmp"
I have written this program as a series of loops which are... I forgot the term, but its where you have a loop inside of a loop. The term escapes me at the moment.
Anyway, the way I am doing this is by first converting the message, which might consist of several statements, into an array of words.
Then, I am converting each of those words into an array of letters, so that I can shift them individually.
Finally, I am merging the array of letters into a single words, and I am merging the array of words back into a single message.
The problem I am running into is that whenever I am trying to use the map and map! methods, I cannot get the shifted letters to retain their value. I come from a C/C++ background, and in those languages I wouldn't have a problem with doing this because I understand how pointers and references work, but I don't know how this works in Ruby.
My question is: How can I get the values of an array to be changed inside of a loop, and not reset back to their original values once I exit the loop? The commented code is as follows:
def caesar_cipher(message,key)
#Convert message to array
message = message.split(' ')
#Map each word in the array to the cipher method
message.map! do |word|
puts "message is: #{message} and the current word is: #{word}"
#Split each word into an array of characters
word = word.split('')
puts "after splitting word is: #{word.inspect}"
#Map each letter to cipher function
word.map do |letter|
puts "trying to shift the letter: #{letter.inspect}"
#Based on the value of the key, each letter will be shifted to the right key times
key.times do
#Cases when the letter is at the end of the alphabet
case letter
when "z"
letter = "a"
when "Z"
letter = "A"
#By default, each letter will be shifted to the next letter in the alphabet per each iteration of the loop
else
letter = letter.next!
end
puts "the letter is now: #{letter.inspect}"
end
#Join the array of letters back into a single word
word = word.join('')
puts "after joining word is: #{word.inspect}"
end
end
#Join the array of words back into the shifted message
message.join(' ')
end
Your code was mostly fine. I made just two tiny fixes
def caesar_cipher(message,key)
message = message.split(' ')
message.map! do |word|
word = word.split('')
word.map! do |letter| # or word = word.map
key.times do
case letter
when "z"
letter = "a"
when "Z"
letter = "A"
else
letter = letter.next!
end
end
letter # return the next letter from the block
end
word.join('')
end
message.join(' ')
end
puts caesar_cipher('hello', 2)
# >> jgnnq
What you were doing wrong
The values were not retaining changes because you didn't save them (map doesn't change the original array, it returns a changed copy)
Sometimes, return value of word.map was letter.next! (because it was the last expression evaluated in the block), which is a number, not a letter. You need to always return the letter.
Not a direct answer to the question, but you might find a more functional approach useful.
I try to reduce nested loops and conditional branch logic where possible, as they can be quite painful to follow.
def caesar_cipher(message, key)
key.times do
message = message
.split("")
.map(&:ord) # convert each character to ascii number
.map(&:next) # increment ascii number by 1
.map(&:chr) # convert ascii number back to character
.join
.gsub("{", "a") # fix characters out of range
.gsub("[", "A")
end
message
end
I've googled everywhere and can't seem to find an example of what I'm looking for. I'm trying to learn ruby and i'm writing a simple script. The user is prompted to enter letters which are loaded into an array. The script then goes through a file containing a bunch of words and pulls out the words that contain what is in the array. My problem is that it only pulls words out if they are in order of the array. For example...
characterArray = Array.new;
puts "Enter characters that the password contains";
characters = gets.chomp;
puts "Searching words containing #{characters}...";
characterArray = characters.scan(/./);
searchCharacters=characterArray[0..characterArray.size].join;
File.open("dictionary.txt").each { |line|
if line.include?(searchCharacters)
puts line;
end
}
If i was to use this code and enter "dog"
The script would return
dog
doggie
but i need the output to return words even if they're not in the same order. Like...
dog
doggie
rodge
Sorry for the sloppy code. Like i said still learning. Thanks for your help.
PS. I've also tried this...
File.open("dictionary.txt").each { |line|
if line =~ /[characterArray[0..characterArray.size]]/
puts line;
end
}
but this returns all words that contain ANY of the letters the user entered
First of all, you don't need to create characterArray yourself. When you assign result of function to a new variable, it will work without it.
In your code characters will be, for example, "asd". characterArray then will be ["a", "s", "d"]. And searchCharacters will be "asd" again. It seems you don't need this conversion.
characterArray[0..characterArray.size] is just equal to characterArray.
You can use each_char iterator to iterate through characters of string. I suggest this:
puts "Enter characters that the password contains";
characters = gets.chomp;
File.open("dictionary.txt").each { |line|
unless characters.each_char.map { |c| line.include?(c) }.include? false
puts line;
end
}
I've checked it works properly. In my code I make an array:
characters.each_char.map { |c| line.include?(c) }
Values of this array will indicate: true - character found in line, false - character not found. Length of this array equals to count of characters in characters. We will consider line good if there is no false values.
I'm working with mails, and names and subjects sometimes come q-encoded, like this:
=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?=
Is there a way to decode them in Ruby? It seems TMail should take care of it, but it's not doing it.
I use this to parse email subjects:
You could try the following:
str = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
if m = /=\?([A-Za-z0-9\-]+)\?(B|Q)\?([!->#-~]+)\?=/i.match(str)
case m[2]
when "B" # Base64 encoded
decoded = Base64.decode64(m[3])
when "Q" # Q encoded
decoded = m[3].unpack("M").first.gsub('_',' ')
else
p "Could not find keyword!!!"
end
Iconv.conv('utf-8',m[1],decoded) # to convert to utf-8
end
Ruby includes a method of decoding Quoted-Printable strings:
puts "Pablo_Fern=C3=A1ndez".unpack "M"
# => Pablo_Fernández
But this doesn't seem to work on your entire string (including the =?UTF-8?Q? part at the beginning. Maybe you can work it out from there, though.
This is a pretty old question but TMail::Unquoter (or its new incarnation Mail::Encodings) does the job as well.
TMail::Unquoter.unquote_and_convert_to(str, 'utf-8' )
or
Mail::Encodings.unquote_and_convert_to( str, 'utf-8' )
Decoding on a line-per-line basis:
line.unpack("M")
Convert STDIN or file provided input of encoded strings into a decoded output:
if ARGV[0]
lines = File.read(ARGV[0]).lines
else
lines = STDIN.each_line.to_a
end
puts lines.map { |c| c.unpack("M") }.join
This might help anyone wanting to test an email. delivery.html_part is normally encoded, but can be decoded to a straight HTML body using .decoded.
test "email test" do
UserMailer.confirm_email(user).deliver_now
assert_equal 1, ActionMailer::Base.deliveries.size
delivery = ActionMailer::Base.deliveries.last
assert_equal "Please confirm your email", delivery.subject
assert delivery.html_part.decoded =~ /Click the link below to confirm your email/ # DECODING HERE
end
The most efficient and up to date solution it seems to use the value_decode method of the Mail gem.
> Mail::Encodings.value_decode("=?UTF-8?Q?Greg_of_Google?=")
=> "Greg of Google"
https://www.rubydoc.info/github/mikel/mail/Mail/Encodings#value_decode-class_method
Below is Ruby code you can cut-and-paste, if inclined. It will run tests if executed directly with ruby, ruby ./copy-pasted.rb. As done in the code, I use this module as a refinement to the String core class.
A few remarks on the solution:
Other solutions perform .gsub('_', ' ') on the unpacked string. However, I do not believe this is correct, and can result in an incorrect decoding depending on the charsets. RFC2047 Section 4.2 (2) indicates "_ always represents hexidecimal 20", so it seems correct to first substitute =20 for _ then rely on the unpack result. (This also makes the implementation more elegant.) This is also discussed in an answer to a related question.
To be more instructive, I have written the regular expression in free-spacing mode to allow comments (I find this generally helpful for complex regular expressions). If you adjust the regular expression, take note that free-spacing mode changes the matching of white-space, which must then be done escaped or as a character class (as in the code). I've also added the regular expression on regex101, so you can read an explanation of the named capture groups, lazy quantifiers, etc. and experiment yourself.
The regular expression will absorb space ( ; but not TAB or newline) between multiple Q-encoded phrases in a single string, as shown with string test_4. This is because RFC2047 Section 5 (1) indicates that multiple Q encoded phrases must be separated from each other by linear white-space. Depending on your use-case, absorbing the white-space may not be desired.
The regular expression code named capture permits unexpected quoted printable codes (other than [bBqQ] so that a match will occur and the code can raise an error. This helps me to detect unexpected values when processing text. Change the regular expression named capture for code to [bBqQ] if you do not want this behaviour. (There will be no match and the original string will be returned.)
It makes use of the global Regexp.last_match as a convenience in the gsub block. You may need to take care if using this in multi-threaded code, I have not given this any consideration.
Additional references and reading:
https://en.wikipedia.org/wiki/Quoted-printable
https://en.wikipedia.org/wiki/MIME#Encoded-Word
require "minitest/autorun"
module QuotedPrintableDecode
class UnhandledCodeError < StandardError
def initialize(code)
super("Unhandled quoted printable code: '#{code}'.")
end
end
##qp_text_regex = %r{
=\? # Opening literal: `=?`
(?<charset>[^\?]+) # Character set, e.g. "Windows-1252" in `=?Windows-1252?`
\? # Literal: `?`
(?<code>[a-zA-Z]) # Encoding, e.g. "Q" in `?Q?` (`B`ase64); [BbQq] expected, others raise
\? # Literal: `?`
(?<text>[^\?]+?) # Encoded text, lazy (non-greedy) matched, e.g. "Foo_bar" in `?Foo_bar?`
\?= # Closing literal: `?=`
(?:[ ]+(?==\?))? # Optional separating linear whitespace if another Q-encode follows
}x # Free-spacing mode to allow above comments, also changes whitespace match
refine String do
def decode_q_p(to: "UTF-8")
self.gsub(##qp_text_regex) do
code, from, text = Regexp.last_match.values_at(:code, :charset, :text)
q_p_charset_to_charset(code, text, from, to)
end
end
private
def q_p_charset_to_charset(code, text, from, to)
case code
when "q", "Q"
text.gsub("_", "=20").unpack("M")
when "b", "B"
text.unpack("m")
else
raise UnhandledCodeError.new(code)
end.first.encode(to, from)
end
end
end
class TestQPDecode < Minitest::Test
using QuotedPrintableDecode
def test_decode_single_utf_8_phrase
encoded = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
assert_equal encoded.decode_q_p, "J. Pablo Fernández"
end
def test_decoding_preserves_space_between_unencoded_phrase
encoded = "=?utf-8?Q?Alfred_Sanford?= <me#example.com>"
assert_equal encoded.decode_q_p, "Alfred Sanford <me#example.com>"
end
def test_decodinge_multiple_adjacent_phrases_absorbs_separating_whitespace
encoded = "=?Windows-1252?Q?Foo_-_D?= =?Windows-1252?Q?ocument_World=9617=96520;_Recor?= =?Windows-1252?Q?d_People_to_C?= =?Windows-1252?Q?anada's_History?="
assert_equal encoded.decode_q_p, "Foo - Document World–17–520; Record People to Canada's History"
end
def test_decoding_string_without_encoded_phrases_preserves_original
encoded = "Contains no QP phrases"
assert_equal encoded.decode_q_p, encoded
end
def test_unhandled_code_raises
klass = QuotedPrintableDecode::UnhandledCodeError
message = "Unhandled quoted printable code: 'Z'."
encoded = "=?utf-8?Z?Unhandled code Z?="
raised_error = assert_raises(klass) { encoded.decode_q_p }
assert_equal message, raised_error.message
end
end