Keys of a hash whose values sum to a particular value - ruby

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}

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.

How to return the elements in the array that the variable falls between

I have a unique sorted array: [2,4,6,8,10].
I have a variable called i. If i is 5, I want to return the elements in the array that 5 falls between. In this case [4,6]. If i is 8, then [8,10].
How should I go about this?
I've tried with partition, to some extent. If i happens to be a number directly equal to one of the values in the array. This seems to work:
a=[2,4,6,8,10]
i = 6
a.partition { |v| v < i }.max[0..1] # returns [6,8]
However, if i is a number not directly equal to any of the values in the array. For example 5, it gets a little trickier.
I got it working for the last case:
a=[2,4,6,8,10]
i = 5
partition = a.partition { |v| v < i }
[].tap { |a| a << partition[0].max; a << partition[1].min } # returns [6,8]
While this works, I am looking to see if there is a better way to write this logic.
You could use Enumerable#each_cons.
def mind_the_gap(arr, n)
arr.each_cons(2).find { |l,u| l <= n && n < u }
end
arr = [2,4,6,8,10]
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> nil
mind_the_gap(arr, 10) #=> nil
If you don't want the last two examples to return nil, you could change the method as follows.
def mind_the_gap(arr, n)
rv = arr.each_cons(2).find { |l,u| l <= n && n < u }
return rv unless rv.nil?
n < arr.first ? :low : :high
end
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> :low
mind_the_gap(arr, 10) #=> :high
Another way is to use Enumerable#slice_when.
def mind_the_gap(arr, n)
a = arr.slice_when { |l,u| l <= n && n < u }.to_a
return [a.first.last, a.last.first] unless a.size == 1
n < arr.first ? :low : :high
end
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> :low
mind_the_gap(arr, 10) #=> :high
If you're looking for elements inside a sorted array, the "better way" probably involves bsearch or bsearch_index.
The second element in the pair is the first element in the array that is greater than your variable, so bsearch_index can return it directly. You need to check it isn't nil or 0 before returning the found element and the previous one :
a = [2, 4, 6, 8, 10]
def find_surrounding_pair(array, element)
second_index = array.bsearch_index { |x| x > element }
array[second_index - 1, 2] if second_index && second_index > 0
end
puts find_surrounding_pair(a, 1).nil?
puts find_surrounding_pair(a, 2) == [2, 4]
puts find_surrounding_pair(a, 7) == [6, 8]
puts find_surrounding_pair(a, 8) == [8, 10]
puts find_surrounding_pair(a, 12).nil?
#=> true * 5
The complexity of this method should be O(log n).
what about this
val = 5
a = [2,4,6,8,10] # assuming it's sorted
a.slice(a.rindex {|e| e <= val}, 2)
It doesn't account for the case when the lookup value is equal or bigger the last element of the array. I'd probably append a nil element for this, if that would be appropriate for the problem.
This looks like a good use to check for the inclusion in a range:
a = [2,4,6,8,10]
b = 5
a.each_cons(2).select { |i, j| (i .. j) === b }
# => [[4, 6]]
It's not clear exactly what you mean by "falls between". In the code above 8 would fall between two sets of numbers:
b = 8
a.each_cons(2).select { |i, j| (i .. j) === b }
# => [[6, 8], [8, 10]]
if the test is i <= b <= j. If it's i <= b < j then use ... instead of ..:
a.each_cons(2).select { |i, j| (i ... j) === b }
# => [[8, 10]]
I'm not a big fan of using ... but it simplifies the code.
From the Range documentation:
Ranges constructed using .. run from the beginning to the end inclusively. Those created using ... exclude the end value.
You could change that to:
a.each_cons(2).select { |i, j| i <= b && b <= j }
or:
a.each_cons(2).select { |i, j| i <= b && b < j }
if those work better for your mind. Using a Range is a little slower, but not radically so.

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?

Return variable rather than the value

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"

Resources