How to indent if...any...do...end...end [closed] - ruby

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
I am new to ruby and I wonder how you would indent this code:
if a.any? do |blah|
name = blah[:name][/.* (.*)/, 1]
name = convert_name(name)
text = "#{name}#{blah[:value]}"
text == "b2"
end
puts "found"
exit 1
end

Like so:
if(a.any? { |blah| text = "#{blah[:name]}#{blah[:value]}"; text == "b2"})
puts "found"
exit 1
end
If your any? block is really long, do this:
result = a.any? do |blah|
text = "#{blah[:name]}#{blah[:value]}"
text == "b2"
...
end
if(result)
puts "found"
exit 1
end

I'd probably toss the search logic into a lambda:
are_wanted = lambda do |blah|
name = blah[:name][/.* (.*)/, 1]
name = convert_name(name)
text = "#{name}#{blah[:value]}"
text == "b2"
end
Then once it has a name so that we can tell what it is for at a glance, the if becomes nice and readable:
if a.any?(&are_wanted)
puts "found"
exit 1
end
I find this technique quite useful when you end up with a complicated Enumerable pipeline, things like this for example:
pull_em_apart = lambda { ... }
the_good_ones = lambda { ... }
put_em_back_together = lambda { ... }
array.map(&pull_em_apart)
.select(&the_good_ones)
.map(&put_em_back_together)
when the lambdas are more than a single line are a lot easier to understand than a big mess of inlined blocks.

Part of the challenge is to cleanly accommodate both the if and any? blocks, since both terminate with an end. One way to distinguish between them, then, is to use braces for the inner block, to make it clearer what is going on:
if a.any? { |blah|
name = blah[:name][/.* (.*)/, 1]
name = convert_name(name)
text = "#{name}#{blah[:value]}"
text == "b2" }
then
puts "found"
exit 1
end
Thanks Cary Swoveland for suggesting the then.

I'd write it something like:
if a.any? { |blah| (blah[:name] + blah[:value]) == "b2" }
puts "found"
exit 1
end
Or:
if a.any? { |blah| blah.values_at(:name, :value).join == "b2" }
puts "found"
exit 1
end
The actual test is short enough that it can be done in a single line.
Generally we use braces ({}) for blocks when they return a value or are on a single line.
Interpolating two strings in another string just to join them is smelly. Just concatenate them; It's more obvious what you're doing.
If you're ONLY concerned about how to indent clearly, consider this:
if a.any? do |blah|
name = blah[:name][/.* (.*)/, 1]
name = convert_name(name)
text = "#{name}#{blah[:value]}"
text == "b2"
end
puts "found"
exit 1
end
The any? block should be indented further than the contents of the if block to visually separate them. Beyond the indention... ugh... the code block for any? should be refactored to a single line still.

Another choice:
begin
puts "found"
exit 1
end if a.any? do |blah|
name = blah[:name][/.* (.*)/, 1]
name = convert_name(name)
text = "#{name}#{blah[:value]}"
text == "b2"
end

Related

Why am I getting "not a number" error in this code? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm just hoping someone might be able to help me out with this code:
def write(aFile, number)
index = 1
while (index < number)
aFile.puts(index.to_s)
index += 1
end
end
def read(aFile)
count = aFile.gets
if (is_numeric?(count))
count = count.to_i
else
count = 0
puts "Error: first line of file is not a number"
end
index = 0
while (count < index)
line = aFile.gets
puts "Line read: " + line
end
end
# Write data to a file then read it in and print it out
def main
aFile = File.new("mydata.txt", "w")
if aFile
write(aFile, 11)
aFile.close
else
puts "Unable to open file to write!"
end
aFile = File.new("mydata.txt", "r")
if aFile
read(aFile)
aFile.close
else
puts "Unable to open file to read!"
end
end
# returns true if a string contains only digits
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
main
The result I'm trying to get is this:
Line read: 0
Line read: 1
...
Line read: 10
But I'm getting:
Error: first line of file is not a number
Why is this the case? Something must be wrong with my code.
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
Result of a code block (such as method body) is the last expression evaluated in it. Your true becomes the value of the if and is ignored, because the next expression evaluated is false, which is what is always returned. There are several ways you can improve this.
def is_numeric?(obj)
return true if /[^0-9]/.match(obj).nil?
false
end
def is_numeric?(obj)
/[^0-9]/.match(obj).nil?
end
def is_numeric?(obj)
/[^0-9]/ !~ obj
end
def is_numeric?(obj)
Integer(obj) rescue false
end
And many more.

Making a sorted array of user's input

I'm learning Ruby with 'Learn to Program' by Chris Pine. On chapter 10 I should write a program where the user types as many words as he like and when he's done, he can just press Enter on an empty line and exit.
I came up with this:
puts "Type whatever you want!"
index = 0
word = ''
array = []
while word != nil
word << gets.chomp
array[index] = word
index = index + 1
end
puts ''
puts array.sort
But that doesn't work. What did I miss? Is there another way I could define word without having to repeat it?
The word will not have nil value. It will be an empty string. So you need to check for that:
while word != ""
# or even better
while !word.empty?
Also, you are adding everything to your word. You probably want to assign to it instead:
word = gets.chomp
Per author's comment:
begin
# your code here
end while !word.empty?
# OR more readable
begin
# your code here
end until word.empty?
It seems like there's a simpler solution, if I'm reading the question correctly.
You could do something like this:
user_input = gets.chomp.split(" ").sort
ex)
input: bananas clementine zebra tree house plane mine
output: ["bananas", "clementine", "house", "mine", "plane", "tree", "zebra"]
Here's a simple loop that you could do just for kicks:
arr = []
arr << $_.strip until gets =~ /^\s*$/
puts arr.sort
$_ is a special variable that evaluates to the last input read from STDIN. So basically this reads "Call gets and check if the input is just spaces. If it is then break out of the loop, otherwise append the last input with whitespace removed value onto the array and continue looping."
Or even more fun, a one liner:
puts [].tap {|arr| arr << $_.strip until gets =~ /^\s*$/}.sort
Basically same thing as above except using tap to initialize the variable.
To answer your questions:
Is there another way I could define word without having to repeat it?
Use side effects of assignment. In ruby when you assign a variable the return value of that assignment is the assigned variable, as in:
irb(main):001:0> (variable = 2) == 2
=> true
The idea would be to put the assignment in the your conditional. If I were to write something like this in a comprehensible loop, as opposed to those above, I'd write something like this:
arr = []
while !(word = gets.strip).empty?
arr << word
end
puts arr.sort
Using loop might simplify the code:
a = []
loop do
input = gets.chomp
if input.empty?
break
else
a << input
end
end
a.sort!
puts a

Ruby code efficiency

Is there a way to make this code shorter and simpler?
loop do
if possibleSet.split(" ").map(&:to_i).any? {|e| (e<0 || e>12)}
print "Please enter valid numbers (between 1 and 12): "
possibleSet = gets
errorinput = false
else
errorinput = true
end
break if errorinput
end
Refactored a bit :)
loop do
print "Please enter valid numbers (between 1 and 12): "
possibleSet = gets.chomp
break unless possibleSet.split(" ").map(&:to_i).any? {|e| (e<0 || e>12)}
end
The code below will check input for correctness:
input = loop do
print "Please enter valid numbers (between 1 and 12): "
# ⇓⇓⇓ as many spaces as user wants
input = gets.chomp.split(/\s+/).map(&:to_i) rescue []
break input unless input.empty? || input.any? { |i| !(0..12).include? i }
end
This parses the user input in an array (not exactly the same behavior, but I hope it is cleaner and you can work from there)
set = []
until set.all? {|i| (1..11).include?(i) } && !set.empty? do
set = gets.split(' ').map(&:to_i)
end

Learn Ruby the Hard Way #41 [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
Hi I`m learning from LRtHW and I got stuck....
I have program like this:
require 'open-uri'
WORD_URL = "http://learncodethehardway.org/words.txt"
WORDS = []
PHRASES = {
"class ### < ###\nend" => "Make a class named ### that is-a ###.",
"class ###\n\tdef initialize(###)\n\tend\nend" => "class ### has-a initialize that takes ### parameters.",
"class ###\n\tdef ***(###)\n\tend\nend" =>"class ### has-a function named *** that takes ### parameters.",
"*** = ###.new()" => "Set *** to an instance of class ###.",
"***.***(###)" => "From *** get the *** function, and call it with parameters ###.",
"***.*** = '***'" => "From *** get the *** attribute and set it to '***'."
}
PHRASE_FIRST = ARGV[0] == "english"
open(WORD_URL) do |f|
f.each_line {|word| WORDS.push(word.chomp)}
end
def craft_names(rand_words, snippet, pattern, caps=false)
names = snippet.scan(pattern).map do
word = rand_words.pop()
caps ? word.capitalize : word
end
return names * 2
end
def craft_params(rand_words,snippet,pattern)
names = (0...snippet.scan(pattern).length).map do
param_count = rand(3) + 1
params = (0...param_count).map {|x| rand_words.pop()}
params.join(', ')
end
return names * 2
end
def convert(snippet, phrase)
rand_words = WORDS.sort_by {rand}
class_names = craft_names(rand_words, snippet, /###/, caps=true)
other_names = craft_names(rand_words, snippet,/\*\*\*/)
param_names = craft_params(rand_words, snippet, /###/)
results = []
for sentence in [snippet, phrase]
#fake class name, also copies sentence
result = sentence.gsub(/###/) {|x| class_names.pop}
#fake other names
result.gsub!(/\*\*\*/) {|x| other_names.pop}
#fake parameter list
result.gsub!(/###/) {|x| param_names.pop}
results.push(result)
end
return results
end
# keep going until they hit CTRL-D
loop do
snippets = PHRASES.keys().sort_by { rand }
for snippet in snippets
phrase = PHRASES[snippet]
question, answer = convert(snippet, phrase)
if PHRASE_FIRST
question, answer = answer, question
end
print question, "\n\n> "
odp = gets.chomp
if odp == "exit"
exit(0)
end
#exit(0) unless STDIN.gets
puts "\nANSWER: %s\n\n" % answer
end
end
I understand most of this code, but I have a problem with:
for sentence in [snippet, phrase]
I know that it is a "for" loop and it creates a "sentence" variable, but how does the loop know that it need to look in a key and value of hash "PHRASES"
And my second "wall" is:
question, answer = convert(snippet, phrase)
It looks like it creates and assigns "question" and "answer variables to the "convert" method with "snippet" and "phrase" parameters... again how does it assigns "question" to a key and answer to a value.
I know that this is probably very simple but as for now it blocks my mind :(
For your first question about the for-loop:
Look at where the for-loop is defined. It's inside the convert() method, right? And the convert() method is passed two arguments: one snippet and one phrase. So the loop isn't "looking" for values in the PHRASES hash, you are the one supplying it. You're using the method's arguments.
For your second question about assignment:
In Ruby we can do something called "destructuring assignment". What this means is that we can assign an array to multiple variables, and each variable will hold one value in the array. That's what's happening in your program. The convert() method returns a two-item array, and you're giving a name (question and answer) to each item in the array.
Here's another example of a destructuring assignment:
a, b, c = [1, 2, 3]
a # => returns 1
b # => returns 2
c # returns 3
Try this out in IRB and see if you get the hang of it. Let me know if I can help clarify anything, or if I misunderstood your question. You should never feel bad about asking "simple" questions!

Refactoring ugly ruby code [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 9 years ago.
Improve this question
The code below gets some text, find commas, and returns an array of a split up version without the commas.
class A
def separate_comma_values(text)
txt_len = text.length
if txt_len == 0
return [""]
end
final = []
sub_arry = ""
for i in (0...txt_len)
ch = text[i]
if ch == ","
final << sub_arry
final << ""
sub = ""
else
sub_arry += ch
end
end
return final
end
end
This is a sample input and output:
s = A.new
print s.separate_comma_values("dh,,,dhhd,jhb")
# => ["dh", "", "dh", "", "dh", "", "dhdhhd", ""]
Although it does what I want it to do, I feel that there is something just not right about it. It's just dirty.
I am aware that I can use a built in method provided by ruby to achieve the split.
Edit: I guess this was edit out of my original post. The motivation behind this was to apply the knowledge I found after reading a ruby book.
There's a method in Ruby that does what you want.
http://ruby-doc.org/core-2.0/String.html#method-i-split
2.0.0p0 :001 > "dh,,,dhhd,jhb".split(',')
=> ["dh", "", "", "dhhd", "jhb"]
So, your code might end up being as simple as
def separate_comma_values(text)
text.split(',')
end
Update: Sorry, I missed the part where you mention you already know about split. Oops.
The only proper way to refactor your code is, obviously, to use String#split.
Although, just for fun:
def separate_comma_values(text)
text.each_char.reduce(['']) do |splitted, char|
if char == ','
splitted << ''
else
splitted.last << char
end
next splitted
end
end
Just as a small trick for looking nicer (in my opinion), you should be able to skip the word 'return' at the end of the method, and your 'seperate_comma_values(text)' could be just 'seperate_comma_values text' without the parens (in your text editor, syntax color highlighting makes this not a problem of clarity at all)
This is a little cleaner, although as #depa points out, if you're trying to split based on commas, there's an easier way, and your (and thus this) code doesn't work right.
class A
def separate_comma_values(text)
return [""] if text.empty?
final = []
sub_arry = ""
text.each_char do |ch|
if ch == ","
final << sub_arry
final << ""
else
sub_arry += ch
end
end
return final
end
end
s = A.new
print s.separate_comma_values("dh,,,dhhd,jhb")
puts
This outputs
["dh", "", "dh", "", "dh", "", "dhdhhd", ""]
just as your does.
Here's how I would implement your algorithm:
class A
def separate_comma_values(text)
return [""] if text.empty?
array = []
value = ""
text.each_char do |c|
if c == ","
array << value
value = ""
else
value += c
end
end
array << value if !value.empty?
array
end
end
s = A.new
print s.separate_comma_values("dh,,,dhhd,jhb")
puts
This outputs
["dh", "", "", "dhhd", "jhb"]

Resources