Why don't these string expressions print the same result? - ruby

Why does this expression:
puts "abc" * 5
=> "abcabcabcabcabc"
not equal this expression?
5.times do puts "abc"
abc
abc
abc
abc
abc
=> 5
Could you please explain why they don't print the same result?

The first writes the string "abc" concatenated to itself five times:
"abc"*5 = "abc"+"abc"+"abc"+"abc"+"abc" = "abcabcabcabcabc"
The second piece of code writes "abc" using the puts function 5 times. The puts function writes a newline character after each message, meaning that it writes "abc\n" 5 times.
5.times do puts "abc"
turns to
puts "abc" ->also jumps to the next line
puts "abc" ->also jumps to the next line
puts "abc" ->also jumps to the next line
puts "abc" ->also jumps to the next line
puts "abc" ->also jumps to the next line

you can replace puts with print, which doesn't add the new line at the end
5.times do print "abc"
end
abcabcabcabcabc => 5

Related

Chop last character of string

I want to make program which takes the string and chop last character each time and print result to console:
With an input string of Hello, the result should be:
Hello
Hell
Hel
He
H
This is my code so far:
def test_string
puts "Put your string in: "
string = gets.chomp
while string.length == 0
puts string.chop(/.$/)
end
end
puts test_string
Use chop!:
string = gets.chomp
# Print full string, e.g. "Hello"
puts string
# Print remaining... e.g. "Hell", "Hel", etc.
while string.length != 0
puts string.chop!
end
Following code does not modify the original string
string = gets.chomp
l = string.length
l.times do |i|
puts string[0..(l-i-1)]
end
You can also create an array filling it with the string N times, and for each time, get a character less from it:
str = 'Hello'
Array.new(str.size) { |index| str[0...str.size - index] }.each { |str| p str }
# "Hello"
# "Hell"
# "Hel"
# "He"
# "H

Using STDIN in ruby , how to print out the multiline comments without REGEX

Given STDIN with the following:
=begin This is a multiline comment and con spwan
as many lines as you like. But =begin and =end
should come in the first line only.
=end
Without using regex , how do you print the in between line as well?
(side question, is ARGF expensive since it has to wait for all the input?)
this is a start:
starting = "=begin"
ending = "=end"
ARGF.each do | line |
comments = false
if line.include?(starting) && !line.include?(ending)
incomments = true
end
if !line.include?(starting) && line.include?(ending)
puts line
incomments = false
end
if incomments == true
puts line.lstrip
end
end
expected output is:
This is a multiline comment and con spwan
as many lines as you like. But =begin and =end
should come in the first line only.
The generic answer that works for any number of nested levels:
input = "..." # could be taken from ARGF
input.
split($/).
each_with_object(result: Hash.new {|h, k| h[k] = []}, level: 0) do |line, acc|
acc[:level] += 1 if line.include?('=begin')
(1..acc[:level]).each do |level|
acc[:result]["Level: #{level}"] << line
end
acc[:level] -= 1 if line.include?('=end');
end[:result]
#⇒ {
# "Level: 1" => [
# "=begin This is a multiline comment and con spwan as many lines as you like.",
# "But =begin and =end should come in the first line only.",
# "=end"
# ],
# "Level: 2" => [
# "But =begin and =end should come in the first line only."
# ]
# }
If you need the comments on top level, just get the value for "Level: 1" key and join it with $/ delimiter.

Ruby regex to get text blocks including delimiters

When using scan in Ruby, we are searching for a block within a text file.
Sample file:
sometextbefore
begin
sometext
end
sometextafter
begin
sometext2
end
sometextafter2
We want the following result in an array:
["begin\nsometext\nend","begin\nsometext2\nend"]
With this scan method:
textfile.scan(/begin\s.(.*?)end/m)
we get:
["sometext","sometext2"]
We want the begin and end still in the output, not cut off.
Any suggestions?
You may remove the capturing group completely:
textfile.scan(/begin\s.*?end/m)
See the IDEONE demo
The String#scan method returns captured values only if you have capturing groups defined inside the pattern, thus a non-capturing one should fix the issue.
UPDATE
If the lines inside the blocks must be trimmed from leading/trailing whitespace, you can just use a gsub against each matched block of text to remove all the horizontal whitespace (with the help of \p{Zs} Unicode category/property class):
.scan(/begin\s.*?end/m).map { |s| s.gsub(/^\p{Zs}+|\p{Zs}+$/, "") }
Here, each match is passed to a block where /^\p{Zs}+|\p{Zs}+$/ matches either the start of a line with 1+ horizontal whitespace(s) (see ^\p{Zs}+), or 1+ horizontal whitespace(s) at the end of the line (see \p{Zs}+$).
See another IDEONE demo
Here's another approach, using Ruby's flip-flop operator. I cannot say I would recommend this approach, but Rubiests should understand how the flip-flop operator works.
First let's create a file.
str =<<_
some
text
at beginning
begin
some
text
1
end
some text
between
begin
some
text
2
end
some text at end
_
#=> "some\ntext\nat beginning\nbegin\n some\n text\n 1\nend\n...at end\n"
FName = "text"
File.write(FName, str)
Now read the file line-by-line into the array lines:
lines = File.readlines(FName)
#=> ["some\n", "text\n", "at beginning\n", "begin\n", " some\n", " text\n",
# " 1\n", "end\n", "some text\n", "between\n", "begin\n", " some\n",
# " text\n", " 2\n", "end\n", "some text at end\n"]
We can obtain the desired result as follows.
lines.chunk { |line| true if line =~ /^begin\s*$/ .. line =~ /^end\s*$/ }.
map { |_,arr| arr.map(&:strip).join("\n") }
#=> ["begin\nsome\ntext\n1\nend", "begin\nsome\ntext\n2\nend"]
The two steps are as follows.
First, select and group the lines of interest, using Enumerable#chunk with the flip-flop operator.
a = lines.chunk { |line| true if line =~ /^begin\s*$/ .. line =~ /^end\s*$/ }
#=> #<Enumerator: #<Enumerator::Generator:0x007ff62b981510>:each>
We can see the objects that will be generated by this enumerator by converting it to an array.
a.to_a
#=> [[true, ["begin\n", " some\n", " text\n", " 1\n", "end\n"]],
# [true, ["begin\n", " some\n", " text\n", " 2\n", "end\n"]]]
Note that the flip-flop operator is distinguished from a range definition by making it part of a logical expression. For that reason we cannot write
lines.chunk { |line| line =~ /^begin\s*$/ .. line =~ /^end\s*$/ }.to_a
#=> ArgumentError: bad value for range
The second step is the following:
b = a.map { |_,arr| arr.map(&:strip).join("\n") }
#=> ["begin\nsome\ntext\n1\nend", "begin\nsome\ntext\n2\nend"]
Ruby has some great methods in Enumerable. slice_before and slice_after can help with this sort of problem:
string = <<EOT
sometextbefore
begin
sometext
end
sometextafter
begin
sometext2
end
sometextafter2
EOT
ary = string.split # => ["sometextbefore", "begin", "sometext", "end", "sometextafter", "begin", "sometext2", "end", "sometextafter2"]
.slice_after(/^end/) # => #<Enumerator: #<Enumerator::Generator:0x007fb1e20b42a8>:each>
.map{ |a| a.shift; a } # => [["begin", "sometext", "end"], ["begin", "sometext2", "end"], []]
ary.pop # => []
ary # => [["begin", "sometext", "end"], ["begin", "sometext2", "end"]]
If you want the resulting sub-arrays joined then that's an easy step:
ary.map{ |a| a.join("\n") } # => ["begin\nsometext\nend", "begin\nsometext2\nend"]

Ruby Hangman game, does not work when I enter the full word but only when I enter one letter

Run my code your ruby interpretor, to see my code. Afterwards, try to guess the full word. The program will tell you that your guess was correct but it doesn't end the game if you guess the entire word instead of each letter one by one.
I also want to add a Dictionary to my code to be able to play against the computer instead of with myself or a friend!
def clear_screen
return system('cls') if Gem.win_platform?
system('clear')
end
loop do
incorrect_guesses = 0
puts ''
puts 'Welcome to Hangman, Win or lose your life!'
puts ''
puts 'Choose Category: It can be anything you desire!'
player1_category = gets.chomp
puts ''
puts 'Player 1, Please enter your desired word'
secret_word = gets.chomp.downcase
clear_screen
correct_guess = ['-'] * secret_word.length
clear_screen
puts "The category is: #{player1_category}"
puts 'Player 2, Please enter your guess'
loop do
puts '_ ' * secret_word.length
player2_guess = gets.chomp.downcase
clear_screen
if secret_word.include? player2_guess
secret_word.each_char.with_index do |letter, i|
next unless letter == player2_guess
correct_guess[i] = letter
end
puts "The category is: #{player1_category}"
puts ''
print 'Guess the word: '
print correct_guess.join('')
puts ''
puts 'Correct. Keep trying!!'
puts ''
else
puts "The category is: #{player1_category}"
puts ''
print 'Guess the word: '
print correct_guess.join('')
puts ''
puts "The word doesn't contain that letter '#{player2_guess.upcase}'"
puts ''
incorrect_guesses += 1
end
puts "Incorrect Guesses: #{incorrect_guesses}"
puts ''
if incorrect_guesses == 6
puts ''
puts '|---+---+- '
puts '| |'
puts '| 0'
puts '| |\\'
puts '| /\\'
puts '-+----------'
puts "The Secret Word is '#{secret_word.capitalize!}'"
puts ''
break
end
next unless secret_word == correct_guess.join('')
puts ''
puts ' (#)'
puts ' ^\\|'
puts ' |/^'
puts '____|_____'
puts ''
puts 'You Win!'
puts ''
puts "You correctly guessed the word '#{secret_word.capitalize!}'"
break
end
end
I got to work with the following change to the next unless test:
if secret_word.include? player2_guess
secret_word.each_char.with_index do |letter, i|
next unless player2_guess.include? letter
correct_guess[i] = letter
end
You were comparing the entire entry to a single character. 't' != 'test'
As for a dictionary, the answer in this link should help
Your question is not super clear, but here are a few comments / answers:
Guessing the whole word: You could wrap your current if secret_word.include? player2_guess in another if that tests the length of the input. (This assumes all words are greater than 1 letter). The if statement should test if the user_input.length > 1. If so, evaluate whether the guess is the correct word, etc.
Adding a dictionary: Easiest was would be to hardcode an array of possible word values. If you want them to correspond to a category, you could make a hash like this {'category_1' => [word, word, word], 'category_2' => [word, word, word]}. Then you could pick a random value from the hash (category) and then a random value from the corresponding array.

What is wrong with my if statements?

Working in Ruby, I'm trying to make it so when I enter a line of input it'll read it and match it with a few if statements.
input_stream = $stdin
input_stream.each_line do |line|
puts line
if line == "a"
puts "test 1"
end
if line == "b"
puts "test 2"
end
end
But when I run it, and enter in "a" or "b", this is the output
a
a
b
b
It recognizes that I entered a and b, and prints it back to me, but the if statements don't function as expected. What is the problem here?
Ruby maintains the newline when using each_line. The simplest solution is to drop it with chomp.
input_stream = $stdin
input_stream.each_line do |line|
line.chomp! # The new helpful line
puts line
if line == "a"
puts "test 1"
end
if line == "b"
puts "test 2"
end
end
Because line has \n character at the end if you write this it will work:
input_stream = $stdin
input_stream.each_line do |line|
puts line
if line.chomp == "a"
puts "test 1"
end
if line.chomp == "b"
puts "test 2"
end
end

Resources