How to replace letters with lines? (Ruby) - ruby

I'm trying to build a hangman game and I need to "censor" the random word with lines, so that for example the word "ruby" is shown as four lines: _ _ _ _
The problem is that my method replaces the word with two extra lines.
Here is my method:
class Game
attr_reader :the_word
def initialize
#the_word = random_word
#player_input = Array.new
#attempts_left = 10
end
def random_word
#the_word = File.readlines("../5desk.txt").sample
end
def hide_the_word
#hidden_word = #the_word.split("")
#hidden_word.each {|letter| letter.replace("_")}
puts "Can you guess what this word is? #{#hidden_word.join()}"
puts #the_word #delete this
end
end
If the random word is "ruby" I would like the game to replace the word with four lines, but it always shows two extra lines (with every random word).
Any help is much appreciated!

Your word from a file may contain spaces. You can remove them by using strip() method.
#the_word = File.readlines("../5desk.txt").sample.strip()
But, as #ggorien mentioned, a simpler way to achieve this is:
#hidden_word = "_" * #the_word.size
and change the puts to
puts "Can you guess what this word is? #{#hidden_word}"

Related

Insert text before the end of a file

I am trying to write a script that will insert a text before the last end tag within a Ruby file. For example, I want to insert the following:
def hello
puts "hello!"
end
within the following file, just before the end of the class:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
end
The result should look like this:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
def hello
puts "hello!"
end
end
I have tried to find a regex that would match the last occurence of end and replace it with the block I want to add but all regexes I have tried match the first end only. Tried these:
end(?=[^end]*$)
end(?!.*end)
(.*)(end)(.*)
To replace the string, I do the following (maybe the EOL characters are screwing up the matching?):
file_to_override = File.read("app/controllers/application_controller.rb")
file_to_override = file_to_override.sub(/end(?=[^end]*$)/, "#{new_string}\nend")
EDIT: I also tried with the solution provided in How to replace the last occurrence of a substring in ruby? but strangely, it replaces all occurences of end.
What am I doing wrong? Thanks!
The approach explained in the post is working here, too. You just need to re-organize capturing groups and use the /m modifier that forces . to match newline symbols, too.
new_string = <<EOS
def hello
puts "Hello!"
end
EOS
file_to_override = <<EOS
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :authenticated?, :current_user
def current_user?
session[:current_user]
end
end
EOS
file_to_override=file_to_override.gsub(/(.*)(\nend\b.*)/m, "\\1\n#{new_string}\\2")
puts file_to_override
See IDEONE demo
The /(.*)(\nend\b.*)/m pattern will match and capture into Group 1 all the text up to the last whole word (due to the \n before and \b after) end preceded with a line feed, and will place the line feed, "end" and whatever remains into Group 2. In the replacement, we back-reference the captured substrings with backreferences \1 and \2 and also insert the string we need to insert.
If there are no other words after the last end, you could even use a /(.*)(\nend\s*\z)/m regex.
Suppose you read the file into the string text:
text = <<_
class A
def a
'hi'
end
end
_
and wish to insert the string to_enter:
to_enter = <<_
def hello
puts "hello!"
end
_
before the last end. You could write
r = /
.* # match any number of any character (greedily)
\K # discard everything matched so far
(?=\n\s*end\b) # match end-of-line, indenting spaces, and "end" followed
# by a word break in a positive lookahead
/mx # multi-line and extended/free-spacing regex definition modes
puts text.sub(r, to_enter)
(prints)
class A
def a
'hi'
end
def hello
puts "hello!"
end
end
Note that sub is replacing an empty string with to_enter.
Edit: Answer from Wiktor is exactly what I was looking for. Leaving the following too because it works as well.
Finally, I gave up on replacing using a regex. Instead, I use the position of the last end:
positions = file_to_override.enum_for(:scan, /end/).map { Regexp.last_match.begin(0) }
Then, before writing the file, I add what I need within the string at last position - 1:
new_string = <<EOS
def hello
puts "Hello!"
end
EOS
file_to_override[positions.last - 1] = "\n#{test_string}\n"
File.open("app/controllers/application_controller.rb", 'w') {|file| file.write(file_to_override)}
This works but it doesn't look like idiomatic Ruby to me.
You can also find and replace the last occurence of "end" (note that this will also match the end in # Hello my friend, but see below) like this
# Our basics: In this text ...
original_content = "# myfile.rb\n"\
"module MyApp\n"\
" class MyFile\n"\
" def myfunc\n"\
" end\n"\
" end\n"\
"end\n"
# ...we want to inject this:
substitute = "# this will come to a final end!\n"\
"end\n"
# Now find the last end ...
idx = original_content.rindex("end") # => index of last "end"(69)
# ... and substitute it
original_content[idx..idx+3] = substitute # (3 = "end".length)
This solution is somewhat more old-school (dealing with indexes in strings felt much cooler some years ago) and in this form more "vulnerable" but avoids you to sit down and digest the regexps. Dont get me wrong, regular expressions are a tool of incredible power and the minutes learning them are worth it.
That said, you can use all the regular expressions from the other answers also with rindex (e.g. rindex(/ *end/)).

Error in .each execution (Ruby)

I'm making an auditor with ruby which started off fine this morning (using single word, user inputted content to omit) but now that I've tried to implement a wordlist, it puts the string to search through as many times as there are words in the wordlist, only censoring it once or twice. My code is as follows.
#by Nightc||ed, ©2015
puts "Enter string: "
text = gets.chomp
redact = File.read("wordlist.txt").split(" ")
words = text.split(" ")
redact.each do |beep|
words.each do |word|
if word != beep
print word + " "
else
print "[snip] "
end
end
end
sleep
I kind of understand why it doesn't work but I'm not sure how to fix it.
Any help would be appreciated.
There's an easier way than iterating through each array. The Array#include method can be easily used to see if the word is contained in your redacted list.
Here's some code that should behave how you wanted the original code to behave:
puts "Enter string: "
text = gets.chomp
redact = File.read("wordlist.txt").split(" ")
words = text.split(" ")
words.each do |word|
if redact.include? word
print "[snip] "
else
print word + " "
end
end
Scrubbing text gets very tricky. One thing you want to watch out for is word boundaries. Splitting on spaces will let a lot of beep words get through because of puctuation. Compare the first two results of the sample code below.
Next, assembling the split text back into its intended form with punction, spacing, etc., gets to be quite challenging. You may want to consider using regex for something presuambly as small as user comments. See the third result.
If you're doing this as a learning exercise, great, but if the application is sensitive where you're likely to take heat over failures to bleep words, you may want to look for an existing well-tested library.
#!/usr/bin/env ruby
# Bleeper
scifi_curses = ['friggin', 'gorram', 'fracking', 'dork']
text = "Why splitting spaces won't catch all the friggin bleeps ya gorram, fracking dork."
words = text.split(" ")
words.each do |this_word|
puts "bleep #{this_word}" if scifi_curses.include?(this_word)
end
puts
better_words = text.split(/\b/)
better_words.each do |this_word|
puts "bleep #{this_word}" if scifi_curses.include?(this_word)
end
puts
bleeped_text = text # keep copy of original if needed
scifi_curses.each do |this_curse|
bleeped_text.gsub!(this_curse, '[bleep]')
end
puts bleeped_text
You should get these results:
bleep friggin
bleep fracking
bleep friggin
bleep gorram
bleep fracking
bleep dork
Why splitting spaces won't catch all the [bleep] bleeps ya [bleep], [bleep] [bleep].

Matching English words algorithm stops working when using Ruby bang methods

I am writing a matching algorithm that checks a user-entered word against a huge list of english words to see how many matches it can find. Everything works, except I have two lines of code that are essentially meant to not pick the same letters twice, and they make the whole thing just return a single letter. Here is what I've done:
word_array = []
File.open("wordsEn.txt").each do |line|
word_array << line.chomp
end
puts "Please enter a string of characters with no spaces:"
user_string = gets.chomp.downcase
user_string_array = user_string.split("")
matching_words = []
word_array.each do |word|
one_array = word.split("")
tmp_user_string_array = user_string_array
letter_counter = 0
for i in 0...word.length
if tmp_user_string_array.include? one_array[i]
letter_counter += 1
string_index = tmp_user_string_array.index(one_array[i])
tmp_user_string_array.slice!(string_index)
end
end
if letter_counter == word.length
matching_words << word
end
end
puts matching_words
This part here is what breaks it:
string_index = tmp_user_string_array.index(one_array[i])
tmp_user_string_array.slice!(string_index)
Can anyone see an issue here? It all makes sense to me.
I see what's happening. You're eliminating letters for non-matching words, which prevents matching words from being found.
For example, take this word list:
ant
bear
cat
dog
emu
And this input to your program:
catdog
The first word you look for is ant, which causes the a and t to be sliced out of catdog, leaving cdog. Now the word cat can no longer be found.
The cure is to make sure that your tmp_user_string_array really is a temporary array. Currently it's a reference to the original user_string_array, which means that you're destructively modifying the user input. You should make a copy of it before you start slicing and dicing.
Once you've got that working, you might like to think about more efficient approaches that don't require duplicating and slicing arrays. Consider this: what if you were to sort each word of your lexicon as well as the input string before starting to look for a match? This would turn the word cat into act and the input acatdog into aacdgot. Do you see how you could traverse the sorted word and the sorted input in search of a match without the need to do any slicing?

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.

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