Return variable rather than the value - ruby

I am curious about a feature of the .each method.
a = 1
b = 2
[a,b].each do |x|
puts x
end
Is there a way for ruby to return the variable "a" rather than the value 1?

It doesn't return 1, it returns [1, 2], the each method returns what it iterated over.
> a = 1
=> 1
> b = 2
=> 2
> r = [a, b].each { |x| puts x }
1
2
=> [1, 2]
> p r.inspect
"[1, 2]"
If you're asking if you can "go backwards" from the array value, or the variable inside the iteration block, I don't see how. If you were iterating over a map with key/value pairs, yes.
> m = { a: 1, b: 2}
=> {:a=>1, :b=>2}
> m.each { |k, v| p "#{k} = #{v}" }
"a = 1"
"b = 2"

Related

Ruby - Can't replace last range's element with another one

So I want to merge overlapping ranges and it should like the following:
Input: ranges = [(1..2), (3..6), (5..8)]
Output: expected = [(1..2), (3..8)]
but when the code iterate over the intervals and goes to the else statement I just get a message "function_merge.rb:9:in block in merge': undefined methodend=' for 2..19:Range (NoMethodError)"
I tried to save merged.last.end and interval.end to variables, rewrote the if statement over couple of lines (if interval.end > merged.last.end merged.last.end = interval.end end) but all that didn't work :-(
def merge(intervals)
merged = []
intervals.sort_by! { |interval| interval.begin }
intervals.each do |interval|
if merged.empty? || merged.last.end < interval.begin
merged << interval
else
merged.last.end = interval.end if interval.end > merged.last.end
end
end
return merged
end
I don't understand why I get this error message since "end" is a range method? I just want to "update" the merged.last.end with the interval.end number.
If you have any tips how to solve it, would be very nice :-)
As Sebastian points out, Ranges are immutable. Instead of trying to change the Range you'll have to make a new one.
def merge(intervals)
merged = []
intervals.sort_by! { |interval| interval.begin }
intervals.each do |interval|
if merged.empty? || merged.last.end < interval.begin
merged << interval
else
merged[-1] = Range.new(merged.last.begin, interval.end, interval.exclude_end?)
end
end
return merged
end
It has been explained that ranges are immutable. The question implies the elements covered by the ranges are all comparable (e.g, not ['a'..'z', 1..10]). I assume that the array of ranges does not contain a mix of finite and infinite ranges.
Solution
Code
def distill(arr)
a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
sort_by(&:begin)
return [] if a.empty?
combined = []
curr = a.shift
loop do
break (combined << curr) if a.empty?
nxt = a.shift
if nxt.begin > curr.end
combined << curr
curr = nxt
else
last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
curr = last.exclude_end? ? (curr.begin...last.end) :
curr.begin..last.end
end
end
end
Examples
distill [5..8, 7...9, 9..11, 1...4, 38..37]
#=> [1...4, 5..11]
distill [1.5...2.2, 2.2..3.0, 3.0...4.5, 4.7..5.3, 5.2..4.6]
#=> [1.5...4.5, 4.7..5.3]
distill ['a'..'d', 'c'..'f', 'b'..'g']
# 'a'..'g'
Explanation
See Range#exclude_end?.
The steps for the first example are as follows.
arr = [5..8, 7...9, 9..11, 1...4, 38..37]
a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
sort_by(&:begin)
#=> [1...4, 5..8, 7...9, 9..11]
a.empty?
#=> false, so do not return
combined = []
curr = a.shift
#=> 1...4
a #=> [5..8, 7...9, 9..11]
The calculations within the loop can be best explained by salting the code with puts statements and displaying the results.
loop do
puts "a.empty? #=> true, so break #{combined + [curr]}" if a.empty?
break (combined << curr) if a.empty?
puts "a.empty? #=> false"
nxt = a.shift
puts "nxt=#{nxt}, a=#{a}"
puts "nxt.begin=#{nxt.begin} > #{curr.end} = curr.end = #{nxt.begin > curr.end}"
if nxt.begin > curr.end
combined << curr
puts "combined << #{curr} = #{combined}"
curr = nxt
puts "curr = nxt = #{curr}"
else
last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
puts "last=#{last}, last.exclude_end?=#{last.exclude_end?}"
curr = last.exclude_end? ? (curr.begin...last.end) :
curr.begin..last.end
puts "new value of curr=#{curr}"
end
puts
end
a.empty? #=> false
nxt=5..8, a=[7...9, 9..11]
nxt.begin=5 > 4 = curr.end = true
combined << 1...4 = [1...4]
curr = nxt = 5..8
a.empty? #=> false
nxt=7...9, a=[9..11]
nxt.begin=7 > 8 = curr.end = false
last=7...9, last.exclude_end?=true
new value of curr=5...9
a.empty? #=> false
nxt=9..11, a=[]
nxt.begin=9 > 9 = curr.end = false
last=9..11, last.exclude_end?=false
new value of curr=5..11
a.empty? #=> true, so break [1...4, 5..11]
It is sometimes convenient to be able to return an empty (but valid) range such as 38..37; one should not think of empty ranges as necessarily being an indication that something is amiss.
Alternative solution
If the ranges are all finite, as in the example, and the combined sizes of the ranges is not excessive, one could write the following.
Code
def distill(arr)
arr.flat_map(&:to_a).
uniq.
sort.
chunk_while { |x,y| y == x.next }.
map { |a| a.first..a.last }
end
Examples
distill [5..8, 7...9, 9..11, 1...4, 38..37]
#=> [1..3, 5..11]
distill ['a'..'d', 'c'..'f', 'b'..'g']
# 'a'..'g'
Explanation
The steps for the first example are as follows.
arr = [5..8, 7...9, 9..11, 1...4, 38..37]
a = arr.flat_map(&:to_a)
#=> => [5, 6, 7, 8, 7, 8, 9, 10, 11, 1, 2, 3]
b = a.uniq
#=> [5, 6, 7, 8, 9, 10, 11, 1, 2, 3]
c = b.sort
#=> [1, 2, 3, 5, 6, 7, 8, 9, 10, 11]
d = c.chunk_while { |x,y| y == x.next }
#=> #<Enumerator: #<Enumerator::Generator:0x00005c2683af8dd0>:each>
e = d.map { |a| a.first..a.last }
#=> [1..3, 5..11]
One can convert the enumerator d to an array to see the elements it will generate and pass to chunk_while's block:
d.to_a
#=> [[1, 2, 3], [5, 6, 7, 8, 9, 10, 11]]
See Enumerable#chunk_while. One could alternatively use Enumerable#slice_when.

Keys of a hash whose values sum to a particular value

I have a hash:
a = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
I want to retrieve a set of keys from it such that the sum of their values equals a certain number, for example 5. In such case, the output should be:
Q2 Q5
Please help me on how to get this.
def find_combo(h, tot)
arr = h.to_a
(1..arr.size).find do |n|
enum = arr.combination(n).find { |e| e.map(&:last).sum == tot }
return enum.map(&:first) unless enum.nil?
end
end
h = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
find_combo(h, 5) #=> ["Q2", "Q5"]
find_combo(h, 2) #=> ["Q2"]
find_combo(h, 6) #=> ["Q5", "Q8"]
find_combo(h, 4) #=> ["Q1", "Q5"]
find_combo(h, 8) #=> ["Q2", "Q5", "Q8"]
find_combo(h, 9) #=> ["Q1", "Q2", "Q5", "Q8"]
find_combo(h, 10) #=> nil
Just out of curiosity:
hash = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
arr = hash.to_a
1.upto(hash.size).
lazy.
find do |i|
res = arr.combination(i).find do |h|
h.map(&:last).sum == 5
end
break res if res
end.tap { |result| break result.to_h if result }
#⇒ {"Q2" => 2, "Q5" => 3}

How can I pass in a block to my "bubble sort" method?

The below code is my newbie take on a bubble sort method.
#For each element in the list, look at that element and the element
#directly to it's right. Swap these two elements so they are in
#ascending order.
def bubble_sort (array)
a = 0
b = 1
until (array.each_cons(2).all? { |a, b| (a <=> b) <= 0}) == true do
sort = lambda {array[a] <=> array[b]}
sort_call = sort.call
loop do
case sort_call
when -1 #don't swap
a += 1
b += 1
break
when 0 #don't swap
a += 1
b += 1
break
when 1 #swap
array.insert(a,array.delete_at(b))
a += 1
b += 1
break
else #end of array, return to start
a = 0
b = 1
break
end
end
end
puts array.inspect
end
array = [4, 2, 5, 6, 3, 23, 5546, 234, 234, 6]
bubble_sort(array)
I want to be able to alter this method so that it takes a block of code as an argument and uses this to determine how it sorts.
For example:
array = ["hello", "my", "name", "is", "daniel"]
bubble_sort(array) {array[#a].length <=> array[#b].length}
(When I've tried this I've turned a and b into instance variables throughout the code.)
I have tried using yield but I get undefined method 'length' for nil:NilClass once the end of the array is reached. I've tried adding in things such as
if array[#b+1] == nil
#a = 0
#b = 1
end
This helps but I still end up with weird problems like infinite loops or not being able to sort more than certain amount of elements.
Long story short, I have been at this for hours. Is there a simple way to do what I want to do? Thanks.
The way you're calling your lambda is a bit odd. It's actually completely unnecessary. I refactored your code and cleaned up a bit of the redundancy. The following works for me:
def sorted?(arr)
arr.each_cons(2).all? { |a, b| (a <=> b) <= 0 }
end
def bubble_sort (arr)
a = 0
b = 1
until sorted?(arr) do
# The yield call here passes `arr[a]` and `arr[b]` to the block.
comparison = if block_given?
yield(arr[a], arr[b])
else
arr[a] <=> arr[b]
end
if [-1, 0, 1].include? comparison
arr.insert(a, arr.delete_at(b)) if comparison == 1
a += 1
b += 1
else
a = 0
b = 1
end
end
arr
end
sample_array = [4, 2, 5, 6, 3, 23, 5546, 234, 234, 6]
# Sanity check:
100.times do
# `a` is the value of `arr[a]` in our function above. Likewise for `b` and `arr[b]`.
print bubble_sort(sample_array.shuffle) { |a, b| a <=> b }, "\n"
end
EDIT
A cleaner version:
# In place swap will be more efficient as it doesn't need to modify the size of the arra
def swap(arr, idx)
raise IndexError.new("Index #{idx} is out of bounds") if idx >= arr.length || idx < 0
temp = arr[idx]
arr[idx] = arr[idx + 1]
arr[idx + 1] = temp
end
def bubble_sort(arr)
loop do
sorted_elements = 0
arr.each_cons(2).each_with_index do |pair, idx|
comparison = if block_given?
yield pair.first, pair.last
else
pair.first <=> pair.last
end
if comparison > 0
swap(arr, idx)
else
sorted_elements += 1
end
end
return arr if sorted_elements >= arr.length - 1
end
end
# A simple test
sample_array = [4, 2, 2, 2, 2, 2, 5, 5, 6, 3, 23, 5546, 234, 234, 6]
sample_str_array = ["a", "ccc", "ccccc"]
100.times do
print bubble_sort(sample_array.shuffle) { |a, b| a <=> b }, "\n"
print bubble_sort(sample_str_array.shuffle) { |a, b| a.length <=> b.length }, "\n"
end
You're not too far off. Just a few things:
Make your function take a block argument
def bubble_sort (array, &block)
Check to see if the user has provided a block
if block_given?
# Call user's comparator block
else
# Use the default behavior
end
Call the user's comparator block
block.call(a, b)
In the user-provided block, accept block params for the elements to compare
bubble_sort(array) {|a,b| a.length <=> b.length}
That should put you in the right ballpark.

Overlap ranges in array

I would like to write a program in ruby 1.9.3 ver. which collects unique value ranges and then calculates amount of numbers in these ranges.
For example lets use 3 ranges (1..3), (6..8) and (2..4). It will return array with two ranges (1..4), (6..8) and amount of numbers - 7.
I wrote the following code:
z= []
def value_ranges(start, finish, z)
range = (start..finish)
arr = z
point = nil
if arr.empty?
point = nil
else
arr.each { |uniq|
if overlap?(uniq,range) == true
point = arr.index(uniq)
break
else
point = nil
end
}
end
if point != nil
if arr[point].first >= start && arr[point].end <= finish
range = (start..finish)
elsif arr[point].first >= start
range = (start..arr[point].end)
elsif arr[point].end <= finish
range = (arr[point].first..finish)
else
range = (arr[point].first..arr[point].end)
end
arr[point] = range
else
arr << range
end
print arr
end
def overlap?(x,y)
(x.first - y.end) * (y.first - x.end) >= 0
end
Problem comes when program meets a range which overlaps two already collected ranges.
For example (1..5) (7..9) (11..19) and the next given range is (8..11).
It should link both ranges and return the following result - (1..5),(7..19).
I don't have an idea how to recheck whole array without creating infinite loop. Also what is the best way to calculate amount of numbers in ranges?
Here are two Ruby-like ways of doing it.
arr = [1..3, 6..8, 2..4]
#1 Efficient approach
First calculate the amalgamated ranges:
a = arr[1..-1].sort_by(&:first).each_with_object([arr.first]) do |r,ar|
if r.first <= ar.last.last
ar[-1] = ar.last.first..[ar.last.last,r.last].max
else
ar << r
end
end
#=> [1..4, 6..8]
Then compute the total number of elements in those ranges:
a.reduce(0) { |tot,r| tot + r.size }
#=> [1..4, 6..8].reduce(0) { |tot,r| tot + r.size }
#=> 7
Explanation
b = arr[1..-1]
#=> [6..8, 2..4]
c = b.sort_by(&:first)
#=> [2..4, 6..8]
enum = c.each_with_object([1..3])
#=> #<Enumerator: [2..4, 6..8]:each_with_object([1..3])>
The contents of the enumerator enum will be passed into the block and assigned to the block variables by Enumerator#each, which will call Array#each. We can see the contents of the enumerator by converting it to an array:
enum.to_a
#=> [[2..4, [1..3]], [6..8, [1..3]]]
and we can use Enumerator#next to step through the enumerator. The first element of the enumerator passed to the block by each is [2..4, [1..3]]. This is assigned to the block variables as follows:
r, ar = enum.next
#=> [2..4, [1..3]]
r #=> 2..4
ar #=> [1..3]
We now perform the block calculation
if r.first <= ar.last.last
#=> 2 <= (1..3).last
#=> 2 <= 3
#=> true
ar[-1] = ar.last.first..[ar.last.last,r.last].max
#=> ar[-1] = 1..[3,4].max
#=> ar[-1] = 1..4
#=> 1..4
else # not executed this time
ar << r
end
This is not so mysterious. So I don't have to keep saying "the last range of ar", let me define:
ar_last = ar.last
#=> 1..3
First of all, because we began by sorting the ranges by the beginning of each range, we know that when each element of enum is passed into the block:
ar_last.first <= r.first
For each element of enum passed into the block for which:
r.first <= ar_last.last
we compare r.last with ar_last.last. There are two possibilities to consider:
r.last <= ar_last.last, in which case the two ranges overlap and therefore ar_last would not change; and
r.last > ar_last.last, in which case the upper end of ar_last must be increased to r.last.
Here,
2 = r.first <= ar_last.last = 3
4 = r.last > ar_last.last = 3
so ar_last is changed from 1..3 to 1..4.
each now passes the last element of enum into the block:
r, ar = enum.next
#=> [6..8, [1..4]]
r #=> 6..8
ar #=> [1..4]
if r.first <= ar.last.last
#=> (6 <= 4) => false this time
...
else # executed this time
ar << r
#=> ar << (6..8)
#=> [1..4, 6..8]
end
and
a = ar #=> [1..4, 6..8]
This time, r.first > ar_last.last, meaning the range r does not overlap ar_last, so we append r to ar, and ar_last now equals r.
Lastly:
a.reduce(0) { |tot,r| tot + r.size }
#=> [1..4, 6..8].reduce(0) { |tot,r| tot + r.size }
#=> 7
which we could alternatively write:
a.map(&:size).reduce(:+)
#2 Easy but inefficient
Here is an easy, but not especially efficient, method that uses Enumerable#slice_when, newly-minted in v2.2.
arr = [(1..3), (6..8), (2..4)]
To calculate the amagamated ranges:
a = arr.flat_map(&:to_a)
.uniq
.sort
.slice_when { |i,j| i+1 != j }
.map { |ar| (ar.first..ar.last) }
#=> [1..4, 6..8]
The total number of elements in those ranges is calculated as in #1
Explanation
Here are the steps:
b = arr.flat_map(&:to_a)
#=> [1, 2, 3, 6, 7, 8, 2, 3, 4]
c = b.uniq
#=> [1, 2, 3, 6, 7, 8, 4]
d = c.sort
#=> [1, 2, 3, 4, 6, 7, 8]
e = d.slice_when { |i,j| i+1 != j }
#=> #<Enumerator: #<Enumerator::Generator:0x007f81629584f0>:each>
a = e.map { |ar| (ar.first..ar.last) }
#=> [1..4, 6..8]
We can see the contents of the enumerator e by converting it to an array:
e.to_a
#=> [[1, 2, 3, 4], [6, 7, 8]]

Ruby count range of values in an array

I have an array:
[1,2,3,4,5,6,7,8,9,10]
and I need to count three separate things.
1) All values less than or equal to 6.
2) All values equal to 7 or 8.
3) All values greater than 8.
What is the best way to do this without counting individual values and adding them all together?
Use Enumerable#count with a block.
[1,2,3,4,5,6,7,8,9,10].count { |val| val <= 6 }
[1,2,3,4,5,6,7,8,9,10].count { |val| val == 7 || val == 8 }
[1,2,3,4,5,6,7,8,9,10].count { |val| val > 8 }
arr = [1,2,3,4,5,6,7,8,9,10]
arr.select {|e| e <= 6}.size
#=> 6
arr.select {|e| e == 7 || e == 8}.size
#=> 2
arr.select {|e| e > 8}.size
#=> 2
How about this:
>> hash = Hash.new(0)
>> arr = [1,2,3,4,5,6,7,8,9,10]
>> arr.each { |e| hash[(e-7.5).truncate<=>0]+=1 }
>> hash
=> {-1=>6, 0=>2, 1=>2}
Or even more succinct:
>> arr = [1,2,3,4,5,6,7,8,9,10]
>> arr.each_with_object(Hash.new(0)) { |e,h| h[(e-7.5).truncate<=>0]+=1 }
=> {-1=>6, 0=>2, 1=>2}
More interesting, imo, is to answer #Luigi's three questions with a single statement (containing no semicolons). Here are two ways to do it:
a = [1,2,3,4,5,6,7,8,9,10]
a.inject([0,0,0]) {|arr,i| [arr[0]+(i<7 ? 1:0), arr[1]+((i>6 and i<9) ? 1:0), arr[2]+(i>8 ? 1:0)]}
a.slice_before(7).to_a.map {|arr| arr.slice_before(9).to_a}.flatten(1).map(&:size)
# => [6, 2, 2]
Edit: another, inspired by #staafl's answer:
arr.group_by {|e| (e+1)/2-4<=>0}.values.map(&:size)
Can you suggest others?

Resources