Ruby STDIN.getch and printf - ruby

I was trying to make a basic spelling console app that using MacOS text to speech to say a word and the user is suppose to spell the word.
I only want to output alphabetic characters, so I used STDIN.getch instead if gets but I found when I spell the word quickly the outputted character get jumbled and duplicated, even though the outputted word is what I expect.
What am I doing wrong that it does not just output the letter that is being said and included in the final word?
Output when I spell horse quickly:
hoorsrese
horse
Code:
class Speaker
def say(text)
`say #{text}`
end
end
require 'io/console'
class SpellMaster
def initialize(speaker = Speaker.new)
#speaker = speaker
end
def spell(word = 'word')
#speaker.say "Spell #{word}"
spelling = get_spelling
if spelling == word
#speaker.say('correct')
else
#speaker.say('incorrect')
end
gets
end
def get_spelling
char = ''
word = ''
while char != "\r"
char = get_letter
word += char
end
printf("\n" + word)
word.chomp
end
def get_letter
char = STDIN.getch
return '' unless /[a-z]/.match?(char) || char == "\r"
print char
#speaker.say(char)
char
end
end
SpellMaster.new.spell
edit: to remove class variable

Related

Why am I accidentally creating palindromes in Ruby?

I am using Ruby to take in a string and reverse the order of the letters (so that the last letter becomes the first, etc.)
When I use the below code, I accidentally create a palindrome by taking half of the string and repeating it:
def reverse(word)
i = 0
new_word = word
while i < word.length
new_word [i] = word [word.length - i - 1]
i += 1
end
return new_word
end
puts reverse("cat") # => "tac"
puts reverse("programming") # => "gnimmargorp"
puts reverse("bootcamp") # => "pmactoob"
However, when I use the below code, I get it right:
def reverse(word)
i = 0
new_word = ""
while i < word.length
new_word [i] = word [word.length - i - 1]
i += 1
end
return new_word
end
puts reverse("cat") # => "tac"
puts reverse("programming") # => "gnimmargorp"
puts reverse("bootcamp") # => "pmactoob"
I just changed word to "" (line 4) and it works. Why?
My suspicion is that the string (word) itself is changing with each iteration, but it isn't supposed to do that is it?
Thank you all.
My suspicion is that the string (word) itself is changing with each iteration, but it isn't supposed to do that is it?
Your suspicion is spot-on.
new_word = word
This does not create a new string. It tells new_word to refer to the same list as word. Ruby is one of a handful of languages where strings are actually mutable objects. So when you modify new_word with []=, you're also modifying word. As you've already noticed, you can start with an empty string
new_word = ""
Alternatively, if you want to start with word and modify it (there are certainly some algorithms where doing so can be beneficial), we can use the #dup method, which performs a shallow copy of the data
new_word = word.dup
You can check whether two variables refer to the exact same object (as opposed to simply looking the same) using #equal?
puts(new_word.equal? word)

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

Reverse a string while keeping it in a single line

I'm trying to reverse a string using the code:
puts("Hi now it's going to be done!")
string = gets.chomp.to_s
i = string.length
while i >= 0
puts(string[i])
i = i - 1
end
It prints the string in backward order, but each word is on a single line. How can I keep all of them on a single line?
puts adds a newline to the end of the output if one isn't already present.
print does not. So do this:
while i >=0
print string[i]
i=i-1
end
puts
The final puts is because you want any further printing to be on a new line.
Try this:
"Hi now it's going to be done!".chars.inject([]) { |s, c| s.unshift(c) }.join
Or This is a little easier to follow:
string = 'Hi now it's going to be done!'
string.reverse!

How to actually write a newline to a string

How can I make this
content[i].gsub("\n", "\\n")
write (something like) this to a file
str = "some text\n"
I'm writing a piece of code to take a file and build a single string out of it that can be inserted back into the source code your working with, if that helps at all
If I'm mistaken and my error is actually somewhere else in the code, here is:
#!/bin/usr/ruby
#reads a file and parses into a single string declaration in language of choice
#another little snippet to make my job easier when writing lots of code
#programmed by michael ward
# h3xc0ntr0l#gmail.com | gists.github.com/michaelfward
# ***************************************
# example scrips
# with writefiles
# | writefiles [file with paths] [file to write*]
# | makestring [file to write* (actually is read, but same as above)] [lang]
#****************************************
def readFile(path)
fd = File.open(path, "r")
content = []
fd.each_line {|x| content.push(x)}
content = fixnewlines(content)
str = content.join()
str
end
def fixnewlines(content)
content.each_index do |i|
content[i].gsub("\n", "\\n")
end
end
def usage
puts "makestring [file to read] [language output]"
exit
end
langs = {"rb"=>"str =", "js" => "var str =", "c"=> "char str[] ="}
usage unless ARGV.length == 2
lang = ARGV[1]
path = ARGV[0]
str = readFile(path)
if langs[lang] == nil
if lang == "c++" || lang == "cpp" || lang == "c#"
puts "#{lang[c]}#{str}"
else
puts "unrecognized language found. supported languages are"
langs.each_key {|k| puts " #{k}"}
exit
end
else
puts "#{langs[lang]} #{str}"
end
Just remove fixnewlines and change readFile:
def readFile(path)
File.read(path).gsub("\n", '\n')
end
Hope it helps. On Windows use \r\n instead of \n. There's no need to escape slashes inside single brackets.
It depends on which platform you're using. Unix uses \n as line ending characters, whereas windows uses \r\n. It looks like you're replacing all new line characters though with \\n which escapes the new line character which I would not expect to work on either platform.

Ruby Regex not matching

I'm writing a short class to extract email addresses from documents. Here is my code so far:
# Class to scrape documents for email addresses
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
def EmailScraper.scrape(doc)
email_addresses = []
File.open(doc) do |file|
while line = file.gets
temp = line.scan(EmailRegex)
temp.each do |email_address|
puts email_address
emails_addresses << email_address
end
end
end
return email_addresses
end
end
if EmailScraper.scrape("email_tests.txt").empty?
puts "Empty array"
else
puts EmailScraper.scrape("email_tests.txt")
end
My "email_tests.txt" file looks like so:
example#live.com
another_example90#hotmail.com
example3#diginet.ie
When I run this script, all I get is the "Empty array" printout. However, when I fire up irb and type in the regex above, strings of email addresses match it, and the String.scan function returns an array of all the email addresses in each string. Why is this working in irb and not in my script?
Several things (some already mentioned and expanded upon below):
\z matches to the end of the string, which with IO#gets will typically include a \n character. \Z (upper case 'z') matches the end of the string unless the string ends with a \n, in which case it matches just before.
the typo of emails_addresses
using \A and \Z is fine while the entire line is or is not an email address. You say you're seeking to extract addresses from documents, however, so I'd consider using \b at each end to extract emails delimited by word boundaries.
you could use File.foreach()... rather than the clumsy-looking File.open...while...gets thing
I'm not convinced by the Regex - there's a substantial body of work already around:
There's a smarter one here: http://www.regular-expressions.info/email.html (clicking on that odd little in-line icon takes you to a piece-by-piece explanation). It's worth reading the discussion, which points out several potential pitfalls.
Even more mind-bogglingly complex ones may be found here.
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\Z/i # changed \z to \Z
def EmailScraper.scrape(doc)
email_addresses = []
File.foreach(doc) do |line| # less code, same effect
temp = line.scan(EmailRegex)
temp.each do |email_address|
email_addresses << email_address
end
end
email_addresses # "return" isn't needed
end
end
result = EmailScraper.scrape("email_tests.txt") # store it so we don't print them twice if successful
if result.empty?
puts "Empty array"
else
puts result
end
Looks like you're putting the results into emails_addresses, but are returning email_addresses. This would mean that you're always returning the empty array you defined for email_addresses, making the "Empty array" response correct.
You have a typo, try with:
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
def EmailScraper.scrape(doc)
email_addresses = []
File.open(doc) do |file|
while line = file.gets
temp = line.scan(EmailRegex)
temp.each do |email_address|
puts email_address
email_addresses << email_address
end
end
end
return email_addresses
end
end
if EmailScraper.scrape("email_tests.txt").empty?
puts "Empty array"
else
puts EmailScraper.scrape("email_tests.txt")
end
You used at the end \z try to use \Z according to http://www.regular-expressions.info/ruby.html it has to be a uppercase Z to match the end of the string.
Otherwise try to use ^ and $ (matching the start and the end of a row) this worked for me here on Regexr
When you read the file, the end of line is making the regex fail. In irb, there probably is no end of line. If that is the case, chomp the lines first.
regex=/\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
line_from_irb = "example#live.com"
line_from_file = line_from_irb +"/n"
p line_from_irb.scan(regex) # => ["example#live.com"]
p line_from_file.scan(regex) # => []

Resources