Unexpected keyword-else - ruby

I have a method which returns finds out which one of the given numbers differs from the others in evenness and returns the index of it (+1).
def iq_test(numbers)
new_array = numbers.split(" ").collect {|n| n.to_i}
x = new_array.select(&:even?)
y = new_array.select(&:odd?)
if x.count > y.count
new_array.split.each_with_index do |value, index|
"#{index + 1}".to_i if value % 3 != 0
else y.count > x.count
"#{index + 1}".to_i if value % 2 == 0
end
end
end
For example iq_test("2 4 7 8 10") should return 3.
However, I am receiving
syntax error, unexpected keyword_else, expecting keyword_end
and I can't find out where I am not closing some code off.

This is going to be part code review as well as answer. Let's start with the first part:
new_array = numbers.split(" ").collect {|n| n.to_i}
x = new_array.select(&:even?)
y = new_array.select(&:odd?)
Spend time to think of good variable names. I wouldn't use "new_array" as a variable name, because it doesn't provide any clue as to what it contains. Do this kind of thing too many times, and a program becomes incomprehensible.
x and y are really evens and odds, aren't they? Those would be better variable names.
A common Ruby idiom is to use plural names for arrays, singular names for everything else.
The split by default splits on whitespace, so the (" ") is unnecessary.
Be careful with indentation.
Your selects are fine, however there is a shortcut: Enumerable's partition.
This is really my own style, but I use map when processing all values in an array, and collect only when doing something like extracting attributes from an array of objects. (In other words, I use map much more often).
note that (&:to_i) is a bit of a shortcut for {|n| n.to_i}
Rewritten considering the above, this part might look like this:
numbers = input.split.map(&:to_i)
evens, odds = numbers.partition(&:even?)
Now let's look at the rest:
if x.count > y.count
new_array.split.each_with_index do |value, index|
"#{index + 1}".to_i if value % 3 != 0
else y.count > x.count
"#{index + 1}".to_i if value % 2 == 0
end
end
And let's consider the error message you got: unexpected keyword else; expected end. This has everything you need to know to answer the question (and you'll find that most error messages do, if you think about them). It says it found an else where it expected an end. And that's exactly the problem, you need to put end before the else to close the do/end block. Also, your else part is missing your iteration logic.
Other notes:
Again, be careful with indentation. Your ends do not line up with what they're ending. Proper alignment can help catch these kinds of errors. Use of an IDE like Rubymine or a sophisticated text editor with Ruby support can help as well.
else clauses are standalone, you don't put a condition after them. Perhaps you meant elsif as Holger commented.
Using string interpolation ("#{}") converts expressions to a string. Here you're converting index + 1 to a string, and then back to an integer with .to_i which cancels it out, so to speak. Simply index + 1 will do.
Array#index can be used to determine the index of a value.
It's not clear if you want all indices in case there is more than one.
Here's a version considering the above:
if evens.count > odds.count
odds.map{|n| numbers.index(n) + 1}
elsif odds.count > evens.count
evens.map{|n| numbers.index(n) + 1}
end
If you like this kind of thing, bring your working code to http://codereview.stackexchange.com/!

This specific error is because you do not have a closing end for your each_with_index block. To fix this error you need:
def iq_test(numbers)
new_array = numbers.split(" ").collect {|n| n.to_i}
x = new_array.select(&:even?)
y = new_array.select(&:odd?)
if x.count > y.count
new_array.split.each_with_index do |value, index|
"#{index + 1}".to_i if value % 3 != 0
end #end of block
elsif y.count > x.count #in order for this to have a condition, it must be an elsif
"#{index + 1}".to_i if value % 2 == 0
end #end of if statement
#end - remove this extra end
end #end of function

Code
def odd_one_out(str)
evens, odds = str.split.partition { |s| s.to_i.even? }
case [evens.size <=> 1, odds.size <=> 1]
when [0, 1] then evens.first
when [1, 0] then odds.first
else nil
end
end
Examples
odd_one_out "0 3 4 6" #=> "3"
odd_one_out "1 5 7 8" #=> "8"
odd_one_out "0 2 4 6" #=> nil
odd_one_out "1 5 7 9" #=> nil
odd_one_out "0 2 3 5" #=> nil
odd_one_out "3" #=> nil
odd_one_out "8" #=> nil
odd_one_out "" #=> nil
Explanation
See Integer#<=>.
Suppose
str = "0 3 4 6"
then
a = str.split
#=> ["0", "3", "4", "6"]
evens, odds = a.partition { |s| s.to_i.even? }
#=> [["0", "4", "6"], ["3"]]
evens
#=> ["0", "4", "6"]
odds
#=> ["3"]
b = [evens.size <=> 1, odds.size <=> 1]
#=> [1, 0]
b == [0, 1]
#=> false
b == [1, 0]
#=> true
c = odds.first
#=> "3"
The case statement, and therefore the method, returns "3".

Related

How do I do this simple Ruby loop?

How do I make a range from 1 through 25 and then print out only the even numbers (hint: remember modulo)? I used this as my answer:
(1..25).each {|n| print n % 2 == 0}
and got boolean values printed. I want to get the numbers instead. What do I need to change in my code above?
(1..25).each{|n| puts n if n.even?}
or
puts (1..25).select(&:even?)
puts (1..25).select {|n| n % 2 == 0}
You were somewhat misunderstanding the condition
(1..25).each {|n| puts n if n % 2 == 0}
puts (1..25).select {|n| n.even?}
Another way:
def print_even(r)
puts ((r.first.even? ? r.first : r.first+1)..r.last).step(2) { |i| puts i }
end
print_even((1..25))
#=> 2
#=> 4
#=> 6
...
#=> 24
print_even((2..26))
#=> 2
#=> 4
#=> 6
...
#=> 26
...and another:
even = true
(1..25).each { |i| puts i if (even = !even) }

Pulling indexes from an array and variable scope

I'm running through TestFirstRuby and I've been stuck on problem 12, building a Reverse Polish Notation calculator. I've gotten through all of the tests except for the last one, asking me to take a string ("1 2 3 * +" and then "1 2 3 * + 4 5 / -"), and then evaluate the expression.
What I'm trying to do is convert the string to an array, changing the numbers into integers and the operators into symbols, then go through the array and evaluate the expression anytime it comes to an operator.
Here's the relevant part of the code:
def initialize
#expression = ''
end
def tokens(string)
tokens = string.split(' ')
tokens.map! { |digit| /[0-9]/.match(digit) ? digit.to_i : digit.to_sym }
end
def evaluate(string)
#1 2 3 * +
#1 2 3 * + 4 5 - /
total = 0
#expression = tokens(string)
#expression.map!{|item|
index = #expression.index(item)
if item == :+
total = total + (#expression[index-1] + #expression[index-2])
2.times {#expression.delete_at(index-1)}
elsif item == :-
total = total + (#expression[index-1] - #expression[index-2])
2.times {#expression.delete_at(index-1)}
elsif item == :x
total = total + (#expression[index-1] * #expression[index-2])
2.times {#expression.delete_at(index-1)}
elsif item == :/
total = total + (#expression[index-1].to_f / #expression[index-2])
2.times {#expression.delete_at(index-1)}
end
}
total
end
What I WANT to happen is this: for each item in the array, it checks if it matches any of the symbols. If there's a match, it changes the element two spaces back from the symbol into the value of whatever the expression is (so 2 3 * becomes 5 3 *). Then, I"m trying to delete the operator and the integer immediately before it, leaving only the evaluated value. I'm doing this by running delete_at twice on the index just before the operator (ideally, 5 3 * goes to 5 * and then just 5). Then it'll move on to the next element in the array.
What I THINK is going wrong, and am having trouble fixing: I think something's going on with the variable scope here. I'm trying to get the expression to be permanently changed every time it runs the code on whatever element it's currently on in the each loop. For each element, a variable 'index' is set using #expression.index(item). This should reset for each element in the each loop. I THINK what's going on is that the original #expression array is being called on for each iteration of the each loop, unchanged from each iteration of the each loop.
The error I'm getting is saying that when it gets to the '+' at the end of the first test string ('1 2 3 * +'), it's trying to add using :x, meaning that when it's calling for the two variables to add together (#expression[index-1] + #expression[index-2]), it's pulling the symbol, which I thought should have been deleted from #expression already. So what I'm hoping will evaluate as 6 + 1 is being evaluated as 3 + :x, which wouldn't work. It's pulling elements from the original array, instead of pulling from the array as it's changed.
Hopefully I'm explaining this clearly enough. Any advice would be great. I'm thinking there's something with scope going on, but I can't find anything specific to this kind of problem to help me out. I've tried different ways of coding this (.map, .each_with_index, .map.with_index, and others), and I'm getting the same problem each time.
You have a tremendous amount of redundant code. In particular, you replicate the operations for each of the four operators. Here is a more Ruby-like way of implementing your calculator.
Code
def evaluate(string)
arr = create_arr(string)
until arr.size == 1
i = arr.index(arr.find { |e| e.is_a? Symbol })
arr[i-2] = arr[i-2].send(arr[i], arr[i-1])
arr.delete_at(i)
arr.delete_at(i-1)
end
arr.first
end
def create_arr(string)
string.split(/\s+/).map { |e| e =~ /-?[0-9]+/ ? e.to_i : e.to_sym }
end
The line in create_arr could alternatively end : e } (sent accepts a string or symbol for the method), in which case e.is_a? Symbol would be changed to e.is_a? String.
Examples
evaluate("3 4 * 2 / 3 - 2 *") #=> 6
evaluate("10 2 / 3 + 2 / 2 -") #=> 2
evaluate("8 -2 / 1 +") #=> -3
evaluate("5 1 2 + 4 * + 3 -") #=> 14
Explanation
Suppose
string = "2 3 4 * 2 / +"
Step 1
arr = create_arr(string) #=> [2, 3, 4, :*, 2, :/, :+]
arr.size == 1 #=> false
v = arr.find { |e| e.is_a? Symbol } #=> :*
i = arr.index(v) #=> 3
arr[i-2] = arr[i-2].send(arr[i], arr[i-1])
# arr[1] = arr[1].send(arr[3], arr[2])
# arr[1] = 3.send(:*, 4) #=> 12
arr #=> [2, 12, 4, :*, 2, :/, :+]
arr.delete_at(i) #=> :*
arr #=> [2, 12, 4, 2, :/, :+]
arr.delete_at(i-1) #=> 4
arr #=> [2, 12, 2, :/, :+]
Step 2
arr.size == 1 #=> false
v = arr.find { |e| e.is_a? Symbol } #=> :/
i = arr.index(v) #=> 3
arr[i-2] = arr[i-2].send(arr[i], arr[i-1])
# arr[1] = arr[1].send(arr[3], arr[2])
# arr[1] = 12.send(:/, 2) #=> 6
arr #=> [2, 6, 2, :/, :+]
arr.delete_at(i) #=> :/
arr #=> [2, 6, 2, :+]
arr.delete_at(i-1) #=> 2
arr #=> [2, 6, :+]
Step 3
arr.size == 1 #=> false
v = arr.find { |e| e.is_a? Symbol } #=> :+
i = arr.index(v) #=> 2
arr[i-2] = arr[i-2].send(arr[i], arr[i-1])
# arr[0] = arr[0].send(arr[2], arr[1])
# arr[0] = 2.send(:+, 6) #=> 8
arr #=> [8, 6, :+]
arr.delete_at(i) #=> :+
arr #=> [8, 6]
arr.delete_at(i-1) #=> 6
arr #=> [8]
Step 4
arr.size == 1 #=> true
arr.first #=> 8

Checking to see if 2 numbers in array sum to 0 in Ruby

I've been going at this problem for a few hours, and I can't see why I can't get it to run properly. The end game to this method is having 2 numbers in an array equaling zero when added together. Here is my code:
def two_sums(nums)
i = 0
j = -1
while i < nums.count
num_1 = nums[i]
while j < nums.count
num_2 = nums[j]
if num_1 + num_2 == 0
return "There are 2 numbers that sum to zero & they are #{num_1} and #{num_2}."
else
return "Nothing adds to zero."
end
end
i += 1
j -= 1
end
end
The problem I'm having is unless the first and last number in the array are the positive and negative of the same number, this will always return false.
For example, if I had an array that was [1, 4, 6, -1, 10], it should come back true. I'm sure my 2 while statement is the cause of this, but I can't think of a way to fix it. If someone could point me in the right direction, that would be helpful.
You can find the first pair that adds up to 0 like this:
nums.combination(2).find { |x, y| x + y == 0 }
#=> returns the first matching pair or nil
Or if you want to select all pairs that add up to 0:
nums.combination(2).select { |x, y| x + y == 0 }
#=> returns all matching pairs or an empty array
Therefore you can implement your method like this:
def two_sums(nums)
pair = nums.combination(2).find { |x, y| x + y == 0 }
if pair
"There are 2 numbers that sum to zero & they are #{pair.first} and #{pair.last}."
else
"Nothing adds to zero."
end
end
Or if you want to find all pairs:
def two_sums(nums)
pairs = nums.combination(2).select { |x, y| x + y == 0 }
if pairs.empty?
"Nothing adds to zero."
else
"The following pairs sum to zero: #{pairs}..."
end
end
Here's another way:
Code
def sum_to_zero(arr)
arr.group_by { |e| e.abs }
.values
.select { |a| (a.size > 1 && a.first == 0) || a.uniq.size > 1 }
end
Examples
sum_to_zero [1, 4, 6, -1, 10] #=> [[1, -1]]
sum_to_zero [1, 4, 1, -2, 10] #=> []
sum_to_zero [1, 0, 4, 1, 0, -1] #=> [[1, 1, -1], [0, 0]]
This method is relatively fast. Let's try it with an array of 200,000 elements, each a random number between -500,000 and 500,000.
require 'time'
t = Time.now
arr = Array.new(200_000) { rand(1_000_001) - 500_000 }
arr.size #=> 200000
sum_to_zero(arr).size #=> 16439
Time.now - t
#=> 0.23 (seconds)
sum_to_zero(arr).first(6)
#=> [[-98747, 98747],
# [157848, -157848],
# [-459650, 459650],
# [176655, 176655, -176655],
# [282101, -282101],
# [100886, 100886, -100886]]
If you wish to group the non-negative and negative values that sum to zero:
sum_to_zero(arr).map { |a| a.partition { |e| e >= 0 } }.first(6)
#=> [[[98747], [-98747]],
# [[157848], [-157848]],
# [[459650], [-459650]],
# [[176655, 176655], [-176655]],
# [[282101], [-282101]],
# [[100886, 100886], [-100886]]]
If you only want a single value for each group (a non-negative value, say):
sum_to_zero(arr).map { |a| a.first.abs }.first(6)
#=> [98747, 157848, 459650, 176655, 282101, 100886]
I think the most Ruby way would be:
nums.combination(2).any? { |x,y| (x+y).zero? }
Here's a way that should work well for large arrays. The methods above which go through every possible combination of two numbers are perfectly fine for small cases but will be very slow and memory hungry for arrays with lots of elements.
def two_sums nums
h = Hash.new
nums.each do |n|
return true if h[-n]
h[n] = true
end
false
end
Well, given it's tagged as #ruby, here's the most "ruby way" I could think of tackling this problem:
def two_sums(arr)
numbers = arr.combination(2).select { |a| a.reduce(:+) == 0 }.flatten
if numbers.empty?
"Nothing adds to zero."
else
"There are 2 numbers that sum to zero & they are #{numbers.first} and #{numbers.last}."
end
end
array.combination(2).select{|x|x[0] + x[1] == 0}

Ruby Logic comparisons with Nil

I am having trouble understanding the following code:
vowels_arr = ["a","e","i","o","u"]
(0...(vowels_arr.length - 1)).all? {|i| vowels_arr[i] <= vowels_arr[i + 1]}
When I try to run it WITHOUT the - 1, I get an error saying that I can't compare a string to nil. But what I dont understand is that why do we even need the -1?? The "..." ranger makes it so we are only evaluating "a","e","i","o" (4 out of the 5). Since the total length is 5 and we are already at 4 things to compare, my belief is that the comparison (vowels_arr[i] <= vowels_arr [i+1]) should work without the -1.
Can someone please explain to me why we need the -1 after array length?
Also are there other ways in ruby to get past this comparing to nil error?
It's because of this:
vowels_arr[i + 1]
(0...(vowels_arr.length)) will return all indexes for the array.
(0...(vowels_arr.length)).to_a # => [0, 1, 2, 3, 4]
But then you're trying to get next index from current. If current index is last index (4), this results in an error, because you get nil where you expect a string (because element doesn't exist at non-existent index). That's why you need length - 1, to allow your logic not to go out of array's bounds.
By the way, if you're trying to check if the array is sorted, why not do it more directly?
vowels_arr = ["a","e","i","o","u"]
puts vowels_arr.sort == vowels_arr
# >> true
As Sergio answers, the problem is with vowels_arr[i + 1]. The variable i ranges over the indices of vowels_arr, and hence i + 1 will not necessarily point to an existing index of vowels_arr. Particularly, when i reaches the last index, i + 1 will be greater than the existing indices, and vowels_arr[i + 1] will be nil.
Also as Sergio answers, if your purpose is to see if it is sorted, then doing as Sergio's answer is straightforward, but in general cases, you can do it like this:
vowels_arr.each_cons(2).all?{|e1, e2| e1 <= e2}
vowels_arr = ["a","e","i","o","u"]
p vowels_arr[vowels_arr.length] #=> nil
(0..(vowels_arr.length)).all? {|i| vowels_arr[i] <= vowels_arr[i + 1]}
#=> `<=': comparison of String with nil failed (ArgumentError)
As you are passing the vowels_arr[vowels_arr.length] element to the block,which is nil. In Ruby array's are 0(zero) based. Thus vowels_arr.length gives 5 means elements are in the range of (0..4). see below:
vowels_arr = ["a","e","i","o","u"]
p vowels_arr[0] #=> "a"
p vowels_arr[1] #=> "e"
p vowels_arr[2] #=> "i"
p vowels_arr[3] #=> "o"
p vowels_arr[4] #=> "u"
p vowels_arr[5] #=> nil
p vowels_arr[6] #=> nil
(0..(vowels_arr.length)) means you are passing to the block 0,1,2,3,4,5, and an attempt to access 5 gives nil, as in your array in 5th index is nil. See why the code (0...(vowels_arr.length)).all? {|i| vowels_arr[i] <= vowels_arr[i + 1]} failed by the below debugging with each to see what has been passed to the block:
vowels_arr = ["a","e","i","o","u"]
(0...(vowels_arr.length)).each {|i| p vowels_arr[i],"--",vowels_arr[i+1]}
p (1...3).to_a
Output:
"a"
"--"
"e"
"e"
"--"
"i"
"i"
"--"
"o"
"o"
"--"
"u"
"u"
"--"
nil

ruby regular expression begin method a bit confusing

m = /(.)(.)(\d+)(\d)/.match("THX1138.")
puts m[0]
c = m.captures #=> HX1138
puts c[0] #=> H
puts m.begin(0) #=> 1
puts c[1] #=> X
puts m.begin(1) #=> 1
puts c[2] #=> 113
puts m.begin(2) #=> 2
I was expecting m.begin(1) to return 2 since X is two elements after the beginning of string.
I am reading the book well grounded rubyist which says
To get the information for capture n,
you provide n as the argument to begin
and/or end.
Similarly I was expecing m.begin(2) to rerturn 3.
Read carefully:
Returns the offset of the start of the nth element of the match array in the string.
So the match array is actually [HX1138,H,X,113,8]
SO
m.begin(0) => offset of HX1138 => 1 in "THX1138"
m.begin(1) => offset of H => 1 in "THX1138"
m.begin(2) => offset of X => 2 in "THX1138"

Resources