Refactoring ugly ruby code [closed] - ruby

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"]

Related

Does anyone see the error in this simple Ruby function?

This function is supposed to take a string and return the characters in reverse order.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
puts reversedString
end
however all the tests return false:
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
Does anyone see what the problem is?
Try to use the default String class reverse method like this:
"Hello World".reverse
"Hello World".reverse!
Check Ruby's String class API at https://ruby-doc.org/core-2.4.0/String.html
If you want to make your custom method, you could use a map like this:
string = String.new
"Hello World".chars.each { | c | string.prepend c }
The problem is your function isn't returning its result, it's printing it. It needs to return reversedString.
As a rule of thumb, functions should return their result. Another function should format and print it.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
return reversedString
end
Note: This was probably an exercise, but Ruby already has String#reverse.
It's good that you're writing tests, but the way you're writing them it's hard to tell what went wrong. Look into a Ruby testing framework like MiniTest.
require "minitest/autorun"
class TestReverse < Minitest::Test
def test_reverse
assert_equal "cba", reverse("abc")
assert_equal "a", reverse("a")
assert_equal "", reverse("")
end
end
That would have told you that your function is returning nil.
1) Failure:
TestReverse#test_reverse [test.rb:16]:
Expected: "cba"
Actual: nil
To make this more Ruby-like yet avoid using the built-in String#reverse method you'd do this:
def reverse(string)
string.chars.reverse.join('')
end
Remember that in Ruby the result of the last operation is automatically the return value of the method. In your case the last operation is puts which always returns nil, eating your value. You want to pass it through.
Try to design methods with a simple mandate, that is, this function should focus on doing one job and one job only: reversing a string. Displaying it is beyond that mandate, so that's a job for another method, like perhaps the caller.
To avoid calling any sort of reverse method at all:
def reverse(string)
result = ''
length = string.length
length.times do |i|
result << string[length - 1 - i]
end
result
end
You can often avoid for almost completely and while frequently if you use things like times or ranges (0..n) to iterate over.
puts prints and returns nil, so the whole method returns nil. If, for debugging reasons , you want to inspect what your method is returning, use p which returns it's argument (reversedString in this case).
def reverse(string)
reversedString = ""
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
p reversedString # !!!
end
And all 3 tests return true
If I was going to do this, I'd probably take advantage of an array:
ary = 'foo bar baz'.chars
reversed_ary = []
ary.size.times do
reversed_ary << ary.pop
end
reversed_ary.join # => "zab rab oof"
pop removes the last character from the array and returns it, so basically it's walking backwards through ary, nibbling at the end and pushing each character onto the end of reversed_ary, effectively reversing the array.
Alternately it could be done using a string:
ary = 'foo bar baz'.chars
reversed_str = ''
ary.size.times do
reversed_str << ary.pop
end
reversed_str # => "zab rab oof"
or:
reversed_str += ary.pop
I just saw that #tadman did a similar thing with the string. His would run more quickly but this is more readable, at least to my eyes.

How to exclude an unused case by conditions [closed]

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 7 years ago.
Improve this question
Which is better?
string.each_char do |letter|
unless letter == " "
if letters.has_key?(letter)
letters[letter] = letters[letter].next
else
letters[letter] = 1
end
end
end
or
string.each_char do |letter|
if letter == " "
elsif letters.has_key?(letter)
letters[letter] = letters[letter].next
else
letters[letter] = 1
end
end
It seems awkward to leave an if statement without the body, but it also seems preferable to going a step deeper with unless.
There's a better way to write this code. I didn't know about default Hash values, and that would clean this code up a lot, but I still wanted to know which is preferable: an if statement without body, or unless, or something else.
This is probably the nicest:
letters = Hash.new(0)
string = "aaabbc"
string.each_char do |letter|
if letter != " "
letters[letter] += 1
end
end
# => {"a"=>3, "b"=>2, "c"=>1}
For deciding between your two examples, I would avoid adding extra-depth (more indentation). The second one is also easier to read because it's simple to follow a string of if/else statements. It's almost always preferable to have more readable code than fancy code.
You can set the default value for a hash when constructing:
letters = Hash.new(0)
...
letters[letter] = letters[letter].next
An interesting approach using this is to use some of the map / reduce methods offered by Ruby:
letters = string.chars
.reject{ |letter| letter == " " }
.each_with_object(Hash.new(0)) { |letter, memo|
memo[letter] = memo[letter].next
}

Factoring Polynomial exercise in ruby, unexpected syntax? [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 8 years ago.
Improve this question
I got these errors --
(eval):30: (eval):30: compile error (SyntaxError)
(eval):8: syntax error, unexpected kDO
-- when I ran this ruby code in console --
class Factor
puts "This program will factor a polynomial in the form of \"Ax^2 + Bx + C.\""
aPos = PosOrNeg?("A")
# test
puts aPos
def PosOrNeg?(string = "blank") do
puts "Tell us if \"#{string}\" is positive or negative. Type \"pos\" or \"neg\"."
posOrNegStr = gets.chomp
case posOrNegStr
when "pos"
pos = true
when aPosOrNegStr = false
pos = false
else
while posOrNegStr != "pos" && posOrNegStr != "neg" do
puts "Invalid input. Please type \"pos\" or \"neg\"."
posOrNegStr = gets.chomp
case posOrNegStr
when "pos"
pos = true
when aPosOrNegStr = false
pos = false
else
end
end
end
end
end
Thoughts?
Look at lines:
# test
puts aPos
def PosOrNeg?(string = "blank") do # <- here!!
Here's an ideal syntax for defining a method in Ruby:
def method_name
# method body
end
So, change def PosOrNeg?(string = "blank") do to def PosOrNeg?(string = "blank").
Note: In Ruby the coding style for defining a method or an object is snake_cased, which is pretty famous among the Ruby community. It'd be much better if you name your method to be something like: pos_or_neg?(string = 'blank').

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

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

Ruby modify a piece of a string

Totally new to Ruby. This is a simple homework assignment. The secret_code function needs to take in input string and perform the following actions:
In the first block of letters before a space, capitalize all but the first char
Reverse the string
So if the input were "super duper", the output should be "repud REPUs".
I coded the function as follows:
def secret_code(input)
input.split(" ").first[1..-1].each_char do |i|
input[i] = i.upcase
end
return input.reverse
end
It passes the unit tests, but I am wondering if there is a better way to code it. Is it possible to avoid using the loop? I tried
return input.split(" ").first[1..-1].upcase.reverse
But that didn't quite work. Any thoughts on how to clean this up are appreciated!
"super duper".sub(/(?<=.)\S+/, &:upcase).reverse
How about this:
def secret_code(input)
first_space = input.index(' ')
(input[0] + input[1...first_space].upcase + input[first_space..-1]).reverse
end
Note that in Ruby, the last expression evaluate in a method is always returned, so you can omit the final return.
s = "super duper"
words = s.split(' ')
words.first[1..-1] = words.first[1..-1].upcase
words.each { |word| word.reverse! }
s = words.reverse.join(' ')
puts s # => repud REPUs
Not necessarily any better, but sure, it can be done without a loop...
def f x
(b = [(a = x.split)[0].upcase, *a.drop(1)].join(' ').reverse)[-1] = x[0, 1]
return b
end
You can try the below:
a = "super duper"
p a.gsub(a.split[0...1].join(' '),a.split[0...1].join(' ').capitalize.swapcase).reverse
Output:
"repud REPUs"

Resources