Ruby Logic comparisons with Nil - ruby

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

Related

Unexpected keyword-else

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

Ruby - Spaceship operator won't work in a block for .sort

I'm getting an error when attempting to use the spaceship operator with non alpha-numeric characters in the sort function.
word = "out-classed"
letters = word.downcase.split('')
letters.sort! do |x, y|
if y < 'a'
next
else
value = x <=> y
end
end
I'm getting ArgumentError: comparison of String with String failed, and I'm almost positive this happens with the spaceship operator and not the < comparison.
The interesting part is that when I do this same comparison in irb outside of the context of a sort block, the comparison works. It also works when the word variable only consists of letters.
Can anybody help me to understand why this doesn't work in this specific context alone?
If you attempt to sort a collection, x<=>y must return 0, 1 or -1 for every pair of elements of the collection. If <=> is defined artificially for some pairs (e.g., 'a'<=>'-' #=> 0 and '-'<=>'a' #=> 0), your sort may return erroneous results.
This is because sort algorithms do not necessarily evaluate all pairs of elements in the collection. If, for example, it finds that:
'a' <=> 'b' #=> 0
and
'b' <=> 'c' #=> 0
it will conclude that:
`a` <=> `c` #=> 0
because the collection being sorted must satisfy transitivity: x <== z if x <= y and y <= z.
For example, if the collection is the array ['z', '-', 'a'] and it finds that 'z' <= '-' and '-' <= 'a', it will conclude that 'z' <= 'a' (and not evaluate 'z' <=> 'a').
That's why:
['z', '-', 'a'].sort { |x,y| p [x,y]; (y < 'a') ? 0 : x<=>y }
#-> ["z", "-"]
#-> ["-", "a"]
#=> ["z", "-", "a"]
doesn't work. You have two choices:
Remove the offending elements before sorting:
['z', '-', 'a'].select { |c| ('a'..'z').cover?(c) }.
sort { |x,y| (y < 'a') ? 0 : x<=>y }
#=> ["a", "z"]
or sort all elements of the collection:
['z', '-', 'a'].sort
#=> ["-", "a", "z"]
If the collection contains non-comparable elements (e.g., [1,2,'cat']), you only choice is to remove elements from the array until all remaining elements are comparable.
Instead of next you need to return 0, 1 or -1. Try this:
word = "out-classed"
letters = word.downcase.split('')
letters.sort! do |x, y|
if y < 'a'
0
else
value = x <=> y
end
end
Your problem lies here
if y < 'a'
next
else
...
sort method expects you to return a comparison value between every pair, so when you call next without returning anything, it says that comparison failed.
Try e.g. this:
if y < 'a'
1
else
value = x <=> y
end

How do I break out of a map/collect and return whatever has been collected up to that point?

I'm rewriting this question in code:
many = 1000
# An expensive method.
#
# It returns some data or nil if no result is available.
expensive_method = lambda do
rand(5) == 0 ? nil : "foo"
end
# Now, let's collect some data and stop collecting when no more data is
# available.
# This is concise but doesn't work.
collection = many.times.map do
expensive_method.call || break
end
puts collection.is_a? Array # false
# This is less concise but works.
collection = []
many.times do
collection << (expensive_method.call || break)
end
puts collection.is_a? Array # true
# My inner Rubyist ponders: Is it possible to accomplish this more concisely
# using map?
Sure seems the only way to do this in Ruby is a filter type method then passing results to map. I'm not sure if this works in 1.8, but in 1.9 you could:
[0,1,2,1,0].take_while {|val| val < 2}.map(&:some_function)
Or in the times example
3.times.take_while {|count| count <= 1 } #=> [0,1]
Instead of using map directly, build up your own collection and then use the fact that break returns a value to abort early:
result =
[0, 1, 2, 1, 0].each_with_object([]) { |val, accumulator|
if val < 2
accumulator << val
else
break accumulator
end
}
result # => [0, 1]
If we did just break (instead of break accumulator) then nil would be implicitly returned and result would just be set to nil.
This solution has the advantage of only allocating a single accumulator Array and only having to loop once.
If you really mean "up to the break", [0,1,2,1,0] should result in [0,1], not [0,1,1,0]. The only way in Ruby that I know about is break in a loop. Functional approach could be much slower as you don't actually break:
r =
[0,1,2,1,0].inject([true, []]) do |(f, a), i|
if f
if i > 1
[false, a]
else
[f, a << i]
end
else
[f, a]
end
end
puts r.last.inspect
Compare with:
r = []
[0,1,2,1,0].each do |i|
break if i > 1
r << i
end
puts r.inspect
Tail recursion is out of the question for Ruby, this is how things are done in true functional languages.
Breaking map doesn't work for me, result is nil.
Added: As #dogenpunk pointed out, there is take_while (and drop_while in fact), which is probably a better alternative, only it always creates temporary array which may or may not be the a problem.
irb(main):011:0> 3.times.select {|count| count <= 1}
=> [0, 1]
or
irb(main):014:0> 3.times.reject {|count| count > 1}
=> [0, 1]
How about:
odd_index = my_collection.index{|item| odd_condition(item)}
result = odd_index == 0 ? [] : my_collection[0..odd_index.to_i - 1]
3.times.map do |count|
count > 1 ? nil : rand
end.compact

Skip over iteration in Enumerable#collect

(1..4).collect do |x|
next if x == 3
x + 1
end # => [2, 3, nil, 5]
# desired => [2, 3, 5]
If the condition for next is met, collect puts nil in the array, whereas what I'm trying to do is put no element in the returned array if the condition is met. Is this possible without calling delete_if { |x| x == nil } on the returned array?
My code excerpt is heavily abstracted, so looking for a general solution to the problem.
There is method Enumerable#reject which serves just the purpose:
(1..4).reject{|x| x == 3}.collect{|x| x + 1}
The practice of directly using an output of one method as an input of another is called method chaining and is very common in Ruby.
BTW, map (or collect) is used for direct mapping of input enumerable to the output one. If you need to output different number of elements, chances are that you need another method of Enumerable.
Edit: If you are bothered by the fact that some of the elements are iterated twice, you can use less elegant solution based on inject (or its similar method named each_with_object):
(1..4).each_with_object([]){|x,a| a << x + 1 unless x == 3}
I would simply call .compact on the resultant array, which removes any instances of nil in an array. If you'd like it to modify the existing array (no reason not to), use .compact!:
(1..4).collect do |x|
next if x == 3
x
end.compact!
In Ruby 2.7+, it’s possible to use filter_map for this exact purpose. From the docs:
Returns an array containing truthy elements returned by the block.
(0..9).filter_map {|i| i * 2 if i.even? } #=> [0, 4, 8, 12, 16]
{foo: 0, bar: 1, baz: 2}.filter_map {|key, value| key if value.even? } #=> [:foo, :baz]
For the example in the question: (1..4).filter_map { |x| x + 1 unless x == 3 }.
See this post for comparison with alternative methods, including benchmarks.
just a suggestion, why don't you do it this way:
result = []
(1..4).each do |x|
next if x == 3
result << x
end
result # => [1, 2, 4]
in that way you saved another iteration to remove nil elements from the array. hope it helps =)
i would suggest to use:
(1..4).to_a.delete_if {|x| x == 3}
instead of the collect + next statement.
You could pull the decision-making into a helper method, and use it via Enumerable#reduce:
def potentially_keep(list, i)
if i === 3
list
else
list.push i
end
end
# => :potentially_keep
(1..4).reduce([]) { |memo, i| potentially_keep(memo, i) }
# => [1, 2, 4]

Chunk a Ruby array according to streaks within it

Summary: The basic question here was, I've discovered, whether you can pass a code block to a Ruby array which will actually reduce the contents of that array down to another array, not to a single value (the way inject does). The short answer is "no".
I'm accepting the answer that says this. Thanks to Squeegy for a great looping strategy to get streaks out of an array.
The Challenge: To reduce an array's elements without looping through it explicitly.
The Input: All integers from -10 to 10 (except 0) ordered randomly.
The Desired Output: An array representing streaks of positive or negative numbers. For instance, a -3 represents three consecutive negative numbers. A 2 represents two consecutive positive numbers.
Sample script:
original_array = (-10..10).to_a.sort{rand(3)-1}
original_array.reject!{|i| i == 0} # remove zero
streaks = (-1..1).to_a # this is a placeholder.
# The streaks array will contain the output.
# Your code goes here, hopefully without looping through the array
puts "Original Array:"
puts original_array.join(",")
puts "Streaks:"
puts streaks.join(",")
puts "Streaks Sum:"
puts streaks.inject{|sum,n| sum + n}
Sample outputs:
Original Array:
3,-4,-6,1,-10,-5,7,-8,9,-3,-7,8,10,4,2,5,-2,6,-1,-9
Streaks:
1,-2,1,-2,1,-1,1,-2,5,-1,1,-2
Streaks Sum:
0
Original Array:
-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10
Streaks:
-10,10
Streaks Sum:
0
Note a few things:
The streaks array has alternating positive and negative values.
The sum of the elements streaks array is always 0 (as is the sum of the original).
The sum of the absolute values of the streak array is always 20.
Hope that's clear!
Edit: I do realize that such constructs as reject! are actually looping through the array in the background. I'm not excluding looping because I'm a mean person. Just looking to learn about the language. If explicit iteration is necessary, that's fine.
Well, here's a one-line version, if that pleases you more:
streaks = original_array.inject([]) {|a,x| (a.empty? || x * a[-1] < 0 ? a << 0 : a)[-1] += x <=> 0; a}
And if even inject is too loopy for you, here's a really silly way:
streaks = eval "[#{original_array.join(",").gsub(/((\-\d+,?)+|(\d+,?)+)/) {($1[0..0] == "-" ? "-" : "") + $1.split(/,/).size.to_s + ","}}]"
But I think it's pretty clear that you're better off with something much more straightforward:
streaks = []
original_array.each do |x|
xsign = (x <=> 0)
if streaks.empty? || x * streaks[-1] < 0
streaks << xsign
else
streaks[-1] += xsign
end
end
In addition to being much easier to understand and maintain, the "loop" version runs in about two-thirds the time of the inject version, and about a sixth of the time of the eval/regexp one.
PS: Here's one more potentially interesting version:
a = [[]]
original_array.each do |x|
a << [] if x * (a[-1][-1] || 0) < 0
a[-1] << x
end
streaks = a.map {|aa| (aa.first <=> 0) * aa.size}
This uses two passes, first building an array of streak arrays, then converting the array of arrays to an array of signed sizes. In Ruby 1.8.5, this is actually slightly faster than the inject version above (though in Ruby 1.9 it's a little slower), but the boring loop is still the fastest.
new_array = original_array.dup
<Squeegy's answer, using new_array>
Ta da! No looping through the original array. Although inside dup it's a MEMCPY, which I suppose might be considered a loop at the assembler level?
http://www.ruby-doc.org/doxygen/1.8.4/array_8c-source.html
EDIT: ;)
original_array.each do |num|
if streaks.size == 0
streaks << num
else
if !((streaks[-1] > 0) ^ (num > 0))
streaks[-1] += 1
else
streaks << (num > 0 ? 1 : -1)
end
end
end
The magic here is the ^ xor operator.
true ^ false #=> true
true ^ true #=> false
false ^ false #=> false
So if the last number in the array is on the same side of zero as the number being processed, then add it to the streak, otherwise add it to the streaks array to start a new streak. Note that sine true ^ true returns false we have to negate the whole expression.
Since Ruby 1.9 there's a much simpler way to solve this problem:
original_array.chunk{|x| x <=> 0 }.map{|a,b| a * b.size }
Enumerable.chunk will group all consecutive elements of an array together by the output of a block:
>> original_array.chunk{|x| x <=> 0 }
=> [[1, [3]], [-1, [-4, -6]], [1, [1]], [-1, [-10, -5]], [1, [7]], [-1, [-8]], [1, [9]], [-1, [-3, -7]], [1, [8, 10, 4, 2, 5]], [-1, [-2]], [1, [6]], [-1, [-1, -9]]]
This is almost exactly what OP asks for, except the resulting groups need to be counted up to get the final streaks array.
More string abuse, a la Glenn McDonald, only different:
runs = original_array.map do |e|
if e < 0
'-'
else
'+'
end
end.join.scan(/-+|\++/).map do |t|
"#{t[0..0]}#{t.length}".to_i
end
p original_array
p runs
# => [2, 6, -4, 9, -8, -3, 1, 10, 5, -7, -1, 8, 7, -2, 4, 3, -5, -9, -10, -6]
# => [2, -1, 1, -2, 3, -2, 2, -1, 2, -4]

Resources