Loop won't break and string won't capitalize properly? - ruby

I am trying to get a simple Ruby program to run correctly.
I need it to take user input until the user types q for "quit". It also needs to capitalize the last four letters of the users input and, with input under four letters, to capitalize all letters.
It only works for input over four letters, and, when I type "quit", it gets a nil error.
See the code below.
I am using Eclipse with Ruby 2.0.
puts ("\n" * 10)
loop do
puts "Please enter a word. (To quit type q.)" # Gets the users input
puts ("\n" * 3) #Scroll the screen 3 times
str = gets.chomp #Collect the player's response
puts ("\n" * 3) #Scroll the screen 3 times
length = str.length # Counts length of string
str = str[0..(length-5)] + str[(length-4)..length].upcase # Only makes last four letters of user input capitalized
puts str # Shows on screen the user input with capitalized last four letters
puts ("\n" * 3) #Scroll the screen 3 times
break if str == "q"
end

You need to pay attention to [] when you write code like this:
str = str[0..(length-5)] + str[(length-4)..length].upcase
If an index is negative, it is counted from the end of string. For example:
str = "abcde"
str[-1] # => "e"
str[-2] #=> "d"
See String.[] for more details.
With regard to your question, here is my logic:
break if str == "q"
if str.length < 4
str = str.upcase
else
str = str[0...-4] + str[-4..-1].upcase
end
p str

You need to do some checks on your length. If length is less than 4, than length - 5 will return a negative number and your
str[0..(length-5)] + ...
line will be upset. For example. If you type "str", then your code will try to do:
length = 3
str[0..-2] + str[-1..3]
which doesn't make sense. Also, since you check for str == "q" at the end, this is likely also happening for "q". Move your check and break up in the block, and make sure you don't treat all strings as if they're going to be longer than 4 characters.

Related

Why am I seeing "N"s when I run my code in Ruby?

I'm new at this and have been practicing on codewars- one of the challenges is a caesar cypher challenge and I've got it pretty much solved except for this one issue...
Here's the code I wrote:
def rot13(string)
alpha = "abcdefghijklmnopqrstuvwxyz"
arr = []
string.each_char.with_index do |char, i|
n = alpha.index(char.downcase)
a = (n.to_i + 13) % 26
unless alpha.include?(char.downcase)
arr << char
end
if char == char.upcase
arr << alpha[a].upcase
else
arr << alpha[a]
end
end
return arr.join
end
puts rot13('10+2 is twelve')
and when I run my code it comes back as this-
1N0N+N2N Nvf Ngjryir
Why are the Ns showing up? Anyone know?
The issue is that you're not checking for non-alphabetical characters properly. Going through the logic:
At line 5, you set n to the index of the character in your alphabet string alpha. Because only letters are included, this call to #include? returns nil for all characters that aren't letters (e.g. your string's first character, '1').
At line 6, you set a to n.to_i + 13. Because nil.to_i is zero, this will always be 13 for any character that isn't a letter.
In the block in lines 7-9, you push char to your array because it doesn't exist in alpha (and so you get '1' as the first character of your output).
But then, in line 10, you push alpha[a].upcase as well if char == char.upcase. If char isn't a letter (e.g. initially '1'), it passes this test (because '1' == '1'.upcase) and your code pushes alpha[13].upcase to the output as well, and 'N' is the 13th letter of the alphabet.
Basically, your checks aren't sufficient. You need to account for numbers and other non-alphabetical characters properly each time.

How can I print a string and pause when there's a full stop or "..." in Ruby?

I'm trying to make a program in Ruby that prints text out and pauses both for every full stop "." or "..." in a string. I've got this so far but don't know where to take it next.
def talk(text, speed = 0.008)
pause = speed * 100
text.each_char do |char|
print char
if char == '.'
sleep(pause)
end
sleep(speed)
end
return
end
# e.g. talk("I can see something... It's getting closer... Oh no.")
It pauses fine for one full stop but obviously doesn't work for three in a row as it just repeats the pause three times. I'm more of a beginner and struggling to find a solution. Thanks for reading
Here's my suggestion -- print all the .s as they appear, sleeping before printing the next character.
def talk(text, speed = 0.008)
pause = speed * 100
last_char = nil # keep track of the previous character.
text.each_char do |char|
if char == '.'
# no sleep at all for periods
print('.')
else
# we're going to print something, if the last character was a period,
# pause before doing it.
sleep(pause) if last_char == '.'
print char
# finally, a regular sleep
sleep(speed)
end
# and update the last character we saw
last_char = char
end
end
Of course, there's lots of different ways to solve this problem!
def talk(text, speed = 0.008)
pause = speed * 100
r = /(\.{3}|\.)/
text.split(r).each do |str|
if str.match?(r)
sleep(pause)
else
print str
end
end
puts
end
talk("Mary had .a lit...tle .lamb", 0.04)
#=> Mary had a little lamb
^ ^ ^
A four-second delay occurred after each character indicated by a party hat was printed.
The steps are as follows.
text = "Mary had .a lit...tle .lamb"
speed = 0.04
pause = speed * 100
r = /(\.{3}|\.)/
This regular expression reads, "match three periods or (|) one period in capture group 1" (the capture group being defined by the parentheses)". This is not the same as /(\.|\.{3})/, which would match each period of each group of three individually (that is, \. before | would succeed for every period, part of a group or not).
a = text.split((r))
#=> ["Mary had ", ".", "a lit", "...", "tle ", ".", "lamb"]
See String#split, which explains the need for the capture group, to include the splitting strings in the array returned.
Continuing,
a.each do |str|
if str.match?(r)
sleep(pause)
else
print str
end
end
This prints (without a newline character) "Mary had ", sleeps for four seconds, prints "a lit", sleeps for another four seconds, and so on.
puts
This last statement is needed to print a newline character to terminate the line.
Another way is to make a few small change to the method given in the question.
def talk(text, speed = 0.008)
pause = speed * 100
text.squeeze('.').each_char do |char|
if char == '.'
sleep(pause)
else
print char
end
end
puts
end
See String#squeeze which converts each string of two or more periods to one period.
This method differs from the earlier one in its treatment of two or more than three periods in a row.

Can anyone give me advice on how to replace the number of letters with asterisks?

Here is my code and output below, i would like to have it so that instead of saying how many occurences, it would output the number of times the letter appears in asterisk form.
For exmaple if "a" appeared four times within a sentence the output would produce:
"a": ****
the_file='C:\Users\Jack\Documents\Ruby\Lab1\lyric.txt'
h = Hash.new
f = File.open(the_file, "r")
f.each_line { |line|
words = line.split(//)
words.each { |w|
if h.has_key?(w)
h[w] = h[w] + 1
else
h[w] = 1
end
}
}
# sort the hash by value, and then print it in this sorted order
h.sort{|a,b| a[1]<=>b[1]}.each { |elem|
puts "\"#{elem[0]}\" : #{elem[1]} occurrences"
}
Screenshot of my current program and output
Instead of #{elem[1]} occurences you just need to write #{'*' * elem[1]}
See method description for more details.
I would like to show another possible alternative way to achieve the word count.
Letting apart the file reading, let's consider the following string:
line = 'Here is my code and output below, i would like to have it so that instead of saying how many occurrences, it would output the number of times the letter appears in asterisk form.'
h = Hash.new(0)
line.downcase.each_char{ |ch| h[ch] += 1 if ('a'..'z').include? ch }
h.to_a.sort_by(&:last).reverse.each { |ch, count| puts "#{ch}: " + "*" * count}
Initialise the hash with default = 0 allow you to start the count without checking if key exists: Hash#default.
Iterate over the line by String#each_char
I counted only case insensitive letters, up to you
For sorting change the Hash into an Array with Hash#to_a
For printing the histogram, as shown in other posts
puts "\"#{elem[0]}\": " + '*' * elem[1]
+ for concatenate, string * number is to repeat certain string number times.
With strings in Ruby, you can use math operators against them. So, you know how many times the letter appears (in elem[1]). In that case you can just multiply the asterisks symbol by that amount:
"\"#{elem[0]}: #{'*' * elem[1]}\""

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

Ruby Regex Code String

I have the following logic
def insert_type(type_list, user_input)
case user_input
when user_input == 'library_1'
type_list.slice!('library_' + RegEx for any digit after library_ and stop right there + '__')
type_list << user_input << '__'
when user_input == 'class_A_2'
type_list.slice!('class_' + RegEx for any digit after class_ and stop right there + _A + '__')
type_list << user_input << '__'
end
end
I tried to do the following
[l][i][b][r][a][r][y][_]\d{0,5} #digit from 0 to 99999
It does work, but there should be a more conventional way out there where I could start with l, and ends with the underscore, then add the number since type_list could be:
puts type_list
=> "username_John__userid_58173__userpass_8adsh20__class_A_2__library_0__"
What you want is this:
\w+?(\d{1,5})
Or if you want a specific word, then:
library_(\d{1,5})
It will non-greedily capture the word characters, then add the numerical value to the first capture group.
Basic example: http://regex101.com/r/xE0sM0
Using your output: http://regex101.com/r/aV6yX6 (not sure if you want to match this?)
Explained:
Any word character, including _, non greedy until we find a number
Any number, from 1 to 5 digits (using {0,5} here would actually be 0 to 5 digits)
Wrapping the digit in parentheses () allows the value to be captured.

Resources