Is there anyway to call multiple end statement - ruby

Is there anyway to call multiple end statement
Like I have a for loop and an if-else statement so can I do two ends together like end[2] or end*2
My statement is: a.each do |i| if i<0 then l+=1 elsif i>0 then s+=1 else h+=1 end end
Here you can see there are two end statements can I turn them into one and shorten the code.
Thanks in Advance :)

can I do two ends together like end[2] or end*2
No, there isn't such syntax.
But you could rewrite your code in other ways, e.g. via <=> and tally:
a = [-2, -1, 0, 1, 2, 3]
counts = a.map { |i| i <=> 0 }.tally
l, s, h = counts.values_at(-1, 0, 1)
l #=> 2
s #=> 3
h #=> 1

Related

Ruby's dynamic Inject method not working

In irb to checkout the working of inject method I wrote down simple code to print the count of numbers divisible by 3 but somehow that's malfunctioning:
[1,2,3,4].inject(0) do |count,value|
if value % 3 == 0
count = count + 1
end
end
It is something minor but I am not getting a hold on it.
With inject, you need to return the memo-element on each iteration:
[1, 2, 3, 4].inject(0) do |count, value|
if value % 3 == 0
count = count + 1
end
count
end
#=> 1
Or, if you prefer the one-line version:
[1, 2, 3, 4].inject(0) { |c, v| c += 1 if v % 3 == 0; c }
#=> 1
Worth noting. In some cases, you can substitute inject for each_with_object, and avoid having to return the memo object, but since the return value of the latter is the original object passed in, it only works with objects passed by reference (i.e. not Fixnums.)
[1, 2, 3, 4].each_with_object(0) { |v, c| c += 1 if v % 3 == 0 }
#=> 0
So it turns out it was infact something minor. I didn't return the count at end of each loop so count was nil after first iteration since I didn't return it. Blunder!
[1,2,3,4].inject(0) do |count,value|
if value % 3 == 0
count = count + 1
end
count
end

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}

Looping through an array with step

I want to look at every n-th elements in an array. In C++, I'd do this:
for(int x = 0; x<cx; x+=n){
value_i_care_about = array[x];
//do something with the value I care about.
}
I want to do the same in Ruby, but can't find a way to "step". A while loop could do the job, but I find it distasteful using it for a known size, and expect there to be a better (more Ruby) way of doing this.
Ranges have a step method which you can use to skip through the indexes:
(0..array.length - 1).step(2).each do |index|
value_you_care_about = array[index]
end
Or if you are comfortable using ... with ranges the following is a bit more concise:
(0...array.length).step(2).each do |index|
value_you_care_about = array[index]
end
array.each_slice(n) do |e, *_|
value_i_care_about = e
end
Just use step() method from Range class which returns an enumerator
(1..10).step(2) {|x| puts x}
We can iterate while skipping over a range of numbers on every iteration e.g.:
1.step(10, 2) { |i| print "#{i} "}
http://www.skorks.com/2009/09/a-wealth-of-ruby-loops-and-iterators/
So something like:
array.step(n) do |element|
# process element
end
class Array
def step(interval, &block)
((interval -1)...self.length).step(interval) do |value|
block.call(self[value])
end
end
end
You could add the method to the class Array
What about:
> [1, 2, 3, 4, 5, 6, 7].select.each_with_index { |_,i| i % 2 == 0 }
=> [1, 3, 5, 7]
Chaining of iterators is very useful.
This is a great example for the use of the modulo operator %
When you grasp this concept, you can apply it in a great number of different programming languages, without having to know them in and out.
step = 2
["1st","2nd","3rd","4th","5th","6th"].each_with_index do |element, index|
puts element if index % step == 1
end
#=> "2nd"
#=> "4th"
#=> "6th"

Modify particular items in array while traversing in reverse order in Ruby

Is there a way to modify particular array elements (based on some condition) while traversing it in reverse order in Ruby?
To be more clear lets say,
problem is replace even numbers in [1,2,3,4,5] with x
output should be [1,x,3,x,5] (same array) but replace should happen from right to left..traversing from 5 to 1.
Thanks in Advance!
This works: (arr.length -1).downto(0) { |x| do something with arr[x] }
p [1,2,3,4,5].reverse_each.map{|e| e.odd? ? e : e/2} #[5, 2, 3, 1, 1]
I understand you want to traverse in reverse order, not get the output also reversed. Maybe this:
xs = [1, 2, 3]
xs.reverse_each.with_index { |x, idx| xs[xs.size-1-idx] = x.to_s if x == 2 }
xs #=> [1, "2", 3]
I appreciate and love Ruby's humane syntax, but you may want to consider more verbose options such as:
ary = [1,2,3,4,5]
i = ary.count - 1
while i >= 0 do
ary[i] = "x" if ary[i] % 2 == 0
i -= 1
end
puts ary.join(",")

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

Resources