How to properly use the map and map! methods - ruby

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

Related

Replace specified phrase with * within text

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 *.

What does word='word' and words=[] do in the following code?

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.

Why doesn't this loop stop?

I'm trying to take the string "xxxyyyzzz" and split it up into an array that groups the same letters. So I want the output to be ["xxx","yyy","zzz"]. I'm not sure why this code keeps on looping. Any suggestions?
def split_up(str)
i = 1
result = []
array = str.split("")
until array == []
if array[i] == array[i-1]
i += 1
else
result << array.shift(i).join("")
end
i = 1
end
result
end
puts split_up("xxxyyyzzz")
The looping is because your until condition never exits. You are incrementing i when the successive characters match, but at the end of the loop you are resetting i to 1.
If you edit this section and add this line:
until array == []
puts i # new line
Then you'll see that i is always 1, and the code keeps printing 1 forever.
Delete the line i = 1 line and you'll get the result you want.
Also, you may be interested in reading about the Ruby string scan method, and pattern matching and capture groups, and using look-ahead and look-behind zero-length assertions, which can match boundaries.
Here is how I would personally accomplish splitting a string at letter boundaries:
"xxxyyyzzz".scan(/(.)(\1*)/).map{|a,b| a+b }
=> ["xxx", "yyy", "zzz"]
The scan method is doing this:
. matches any character e.g. "x", and the parentheses capture this.
\1* matches the previous capture any number of time, e.g. "xx", and the parentheses capture this.
Thus $1 matches the first character "x" and $2 matches all the repeats "xx".
The scan block concatenates the first character and its repeats, so returns "xxx".
As mentioned above, this can be solved using scan like this:
def split_up(string)
repeat_alphabets = /(\w)(\1*)/
string.scan(repeat_alphabets).map do |match|
match[0] << match[1]
end
end
Explanation:
The regular expression matches repeating characters, but due to the construction of the regex matches occur as pairs of the alphabet and remaining repeated instances.
m[0] << m[1] joins the matches to form the required string.
map combines the string into an array and returns the array as it being the last statement.

Randomly replace letters in word

I tried to write a function which will be able to randomly change letters in word except first and last one.
def fun(string)
z=0
s=string.size
tab=string
a=(1...s-1).to_a.sample s-1
for i in 1...(s-1)
puts tab[i].replace(string[a[z]])
z=z+1
end
puts tab
end
fun("sample")
My output is:
p
l
a
m
sample
Anybody know how to make it my tab be correct?
it seems to change in for block, because in output was 'plamp' so it's random as I wanted but if I want to print the whole word (splampe) it doesn't working. :(
What about:
def fun(string)
first, *middle, last = string.chars
[first, middle.shuffle, last].join
end
fun("sample") #=> "smalpe"
s = 'sample'
[s[0], s[1..-2].chars.shuffle, s[-1]].join
# => "slpmae"
Here is my solution:
def fun(string)
first = string[0]
last = string[-1]
middle = string[1..-2]
puts "#{first}#{middle.split('').shuffle.join}#{last}"
end
fun('sample')
there are some problems with your function. First, when you say tab=string, tab is now a reference to string, so, when you change characters on tab you change the string characters too. I think that for clarity is better to keep the index of sample (1....n)to reference the position in the original array.
I suggest the usage of tab as a new array.
def fun(string)
if string.length <= 2
return
z=1
s=string.size
tab = []
tab[0] = string[0]
a=(1...s-1).to_a.sample(s-1)
(1...s-1).to_a.each do |i|
tab[z] = string[a[i - 1]]
z=z+1
end
tab.push string[string.size-1]
tab.join('')
end
fun("sample")
=> "spalme"
Another way, using String#gsub with a block:
def inner_jumble(str)
str.sub(/(?<=\w)\w{2,}(?=\w)/) { |s| s.chars.shuffle.join }
end
inner_jumble("pneumonoultramicroscopicsilicovolcanoconiosis") # *
#=> "poovcanaiimsllinoonroinuicclprsciscuoooomtces"
inner_jumble("what ho, fellow coders?")
#=> "waht ho, folelw coedrs?"
(?<=\w) is a ("zero-width") positive look-behind that requires the match to immediately follow a word character.
(?=\w) is a ("zero-width") positive look-ahead that requires the match to be followed immediately by a word character.
You could use \w\w+ in place of \w{2,} for matching two or more consecutive word characters.
If you only want it to apply to individual words, you can use gsub or sub.
*A lung disease caused by inhaling very fine ash and sand dust, supposedly the longest word in some English dictionaries.

How can I stop the lines from repeating?

Look at this code. I got the desired result, which was to scan a person's input to see if it matches an internal array.
sentence = []
compare = []
database_array = ["Mouse", "killer", "Blood", "Vampires", "True Blood", "Immortal" ]
def parser_sentence compare
database_array = ["Mouse", "killer", "Blood", "Vampires", "True Blood", "Immortal"]
initial_index = 0
while compare.count > initial_index
compare.each do |item|
if item == database_array[initial_index]
puts "You found the key word, it was #{item}"
else
puts "Sorry the key word was not inside your sentence"
end
end
initial_index = initial_index + 1
end
end
puts "Please enter in your sentences of words and i will parse it for the key word."
sentence = gets.chomp
compare = sentence.split (" ")
Because each loop is telling it to repeat, it does so, but how can I stop this?
In this case, regex will be more efficient and less error prone than splitting the input string, especially since you have a two-word phrase in the keyword list.
def parser_sentence(sentence)
matching_words = sentence.scan(Regexp.union(database_array))
if matching_words.empty?
puts "Sorry the key word was not inside your sentence"
else
puts "You found the key word, it was #{matching_words.join(" ")}"
end
end
Slight modifications can make it case sensitive (if you need it), or add word boundaries to the keywords so as to not match partial words.
One possible solution that doesn't involve looping is to intersect your compare and database_array arrays, like so:
matching_words = compare & database_array
This will compare both arrays and create a new array containing only elements that are common to both. For example:
# If the user input the sentence "The Mouse is Immortal", then...
compare = ["The", "Mouse", "is", "Immortal"]
# matching_words will contain an array with elements ["Mouse", "Immortal"]
matching_words = compare & database_array
You can then check the length of the array and display out your messages. I believe this can replace your entire function like so:
def parser_sentence compare
matching_words = compare & database_array
if matching_works.length > 0
puts "You found the key word, it was #{matching_words.join(" ")}"
else
puts "Sorry the key word was not inside your sentence"
end
end
Note about the use of join, if you're unfamiliar with that, it basically creates a string using each element in the array separated by the separator string passed in, which in my example is merely a blank space; substitute for your own separate of course, or whatever you want to do with it.

Resources