How to pick top 5 values from a hash? - ruby

I have a hash of ids and their scores, it's something like this:
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
How can I pick the top five and drop the rest ?
I'm doing this:
#orderedObject = #objects.sort_by {|k,v| v}.reverse
=>[[29, 97], [39, 58], [1, 57], [55, 47], [32, 39], [17, 27], [4, 12], [3, 9], [5, 3]]
Then I do this:
only Keys of the #orderedObjects:
#keys = #orderedObject.map { |key, value| key }
which gives me:
=>[29, 39, 1, 55, 32, 17, 4, 3, 5]
ALL I need is [29, 39, 1, 55, 32] the first 5 indexes. But I'm stuck I don't know how to do this.

You can do
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
#objects.sort_by { |_, v| -v }[0..4].map(&:first)
# => [29, 39, 1, 55, 32]
#objects.sort_by { |_, v| -v }.first(5).map(&:first)
# => [29, 39, 1, 55, 32]

May i suggest this more verbose requires ruby > 1.9
Hash[#objects.sort_by{|k,v| -v}.first(5)].keys

A variant of Prof. Arup's answer:
objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
objects.sort_by { |k,v| -v }.first(5).to_h.keys #=> [29, 39, 1, 55, 32]
Now suppose 3=>9 were instead 3=>39 and you wanted the keys corresponding to the top 5 values (which, in this case, would be 6 keys, as 39 is the fifth largest value, 3=>39 and 32=>39), you could first compute:
threshold = objects.values.sort.last(5).min #=> 39
If you wanted the keys to be ordered by the order of values threshold or larger,
objects.select { |_,v| v >= threshold }.sort_by { |_,v| -v }.map(&:first)
#=> [29, 39, 1, 55, 3, 32]
If you don't care about the order,
objects.select { |_,v| v >= threshold }.keys #=> [1, 3, 55, 32, 29, 39]

Related

Subtract Range of Numbers Algorithm

How would I accomplish the following: Take one array of ranges and subtract another array of ranges from it.
For example:
arr0 = [[0,50],[60,80],[100,150]] # 0-50, 60-80, etc.
arr1 = [[4,8],[15,20]] # 4-8, 15-20, etc.
# arr0 - arr1 magic
result = [[0,3],[9,14],[21,50],[60,80],[100,150]] # 0-3, 9-14, etc.
What's the cleanest and most efficient way to do this in Ruby?
This is a deliberately naïve solution. It's not efficient, but easy to comprehend and quite short.
Deconstruct arr0 into a list of numbers:
n1 = arr0.flat_map { |a, b| (a..b).to_a }
#=> [0, 1, ..., 49, 50, 60, 61, ..., 79, 80, 100, 101, ..., 149, 150]
Same for arr1:
n2 = arr1.flat_map { |a, b| (a..b).to_a }
#=> [4, 5, 6, 7, 8, 15, 16, 17, 18, 19, 20]
Then, subtract n2 from n1 and recombine consecutive numbers:
(n1 - n2).chunk_while { |a, b| a.succ == b }.map(&:minmax)
#=> [[0, 3], [9, 14], [21, 50], [60, 80], [100, 150]]

Finding similar objects located in same index position of arrays in Ruby

I have the following hash:
hash = {"1"=>[ 5, 13, "B", 4, 10],
"2"=>[27, 19, "B", 18, 20],
"3"=>[45, 41, "B", 44, 31],
"4"=>[48, 51, "B", 58, 52],
"5"=>[70, 69, "B", 74, 73]}
Here is my code:
if hash.values.all? { |array| array[0] == "B" } ||
hash.values.all? { |array| array[1] == "B" } ||
hash.values.all? { |array| array[2] == "B" } ||
hash.values.all? { |array| array[3] == "B" } ||
hash.values.all? { |array| array[4] == "B" }
puts "Hello World"
What my code does is iterates through an array such that if the same element appears in the same index position of each array, it will output the string "Hello World" (Since "B" is in the [2] position of each array, it will puts the string. Is there a way to condense my current code without having a bunch of or's connecting each index of the array?
Assuming all arrays are always of the same length, the following gives you the column indexes where all values are equal:
hash.values.transpose.each_with_index.map do |column, index|
index if column.all? {|x| x == column[0] }
end.compact
The result is [2] for your hash. So you know that for all arrays the index 2 has the same values.
You can print "Hello World" if the resulting array has at least one element.
How does it work?
hash.values.transpose gives you all the arrays, but with transposed (all rows are now columns) values:
hash.values.transpose
=> [[5, 27, 45, 48, 70],
[13, 19, 41, 51, 69],
["B", "B", "B", "B", "B"],
[4, 18, 44, 58, 74],
[10, 20, 31, 52, 73]]
.each_with_index.map goes over every row of the transposed array while providing an inner array and its index.
We look at every inner array, yielding the column index only if all elements are equal using all?.
hash.values.transpose.each_with_index.map {|column, index| index if column.all? {|x| x == column[0] }
=> [nil, nil, 2, nil, nil]
Finally, we compact the result to get rid of the nil values.
Edit: First, I used reduce to find the column with identical elements. #Nimir pointed out, that I re-implemented all?. So I edited my anwer to use all?.
From #tessi brilliant answer i though of this way:
hash.values.transpose.each_with_index do |column, index|
puts "Index:#{index} Repeated value:#{column.first}" if column.all? {|x| x == column[0]}
end
#> Index:2 Repeated value:B
How?
Well, the transpose already solves the problem:
hash.values.transpose
=> [[5, 27, 45, 48, 70],
[13, 19, 41, 51, 69],
["B", "B", "B", "B", "B"],
[4, 18, 44, 58, 74],
[10, 20, 31, 52, 73]
]
We can do:
column.all? {|x| x == column[0]}
To find column with identical items
Assuming that all the values of the hash will be arrays of the same size, how about something like:
hash
=> {"1"=>[5, 13, "B", 4, 10], "2"=>[27, 19, "B", 18, 20], "3"=>[45, 41, "B", 44, 31], "4"=>[48, 51, "B", 58, 52], "5"=>[70, 69, "B", 74, 73]}
arr_of_arrs = hash.values
=> [[5, 13, "B", 4, 10], [27, 19, "B", 18, 20], [45, 41, "B", 44, 31], [48, 51, "B", 58, 52], [70, 69, "B", 74, 73]]
first_array = arr_of_arrs.shift
=> [5, 13, "B", 4, 10]
first_array.each_with_index do |element, index|
arr_of_arrs.map {|arr| arr[index] == element }.all?
end.any?
=> true
This is not really different from what you have now, as far as performance - in fact, it may be a bit slower. However, it allows for a dynamic number of incoming key/value pairs.
I ended up using the following:
fivebs = ["B","B","B","B","B"]
if hash.values.transpose.any? {|array| array == fivebs}
puts "Hello World"
If efficiency, rather than readability, is most important, I expect this decidedly un-Ruby-like and uninteresting solution probably would do well:
arr = hash.values
arr.first.size.times.any? { |i| arr.all? { |e| e[i] == ?B } }
#=> true
Only one intermediate array (arr) is constructed (e.g, no transposed array), and it quits if and when a match is found.
More Ruby-like is the solution I mentioned in a comment on your question:
hash.values.transpose.any? { |arr| arr.all? { |e| e == ?B } }
As you asked for an explanation of #Phrogz's solution to the earlier question, which is similar to this one, let me explain the above line of code, by stepping through it:
a = hash.values
#=> [[ 5, 13, "B", 4, 10],
# [27, 19, "B", 18, 20],
# [45, 41, "B", 44, 31],
# [48, 51, "B", 58, 52],
# [70, 69, "B", 74, 73]]
b = a.transpose
#=> [[ 5, 27, 45, 48, 70],
# [ 13, 19, 41, 51, 69],
# ["B", "B", "B", "B", "B"],
# [ 4, 18, 44, 58, 74],
# [ 10, 20, 31, 52, 73]]
In the last step:
b.any? { |arr| arr.all? { |e| e == ?B } }
#=> true
(where ?B is shorthand for the one-character string "B") an enumerator is created:
c = b.to_enum(:any?)
#=> #<Enumerator: [[ 5, 27, 45, 48, 70],
# [ 13, 19, 41, 51, 69],
# ["B", "B", "B", "B", "B"],
# [ 4, 18, 44, 58, 74],
# [ 10, 20, 31, 52, 73]]:any?>
When the enumerator (any enumerator) is acting on an array, the elements of the enumerator are passed into the block (and assigned to the block variable, here arr) by Array#each. The first element passed into the block is:
arr = [5, 27, 45, 48, 70]
and the following is executed:
arr.all? { |e| e == ?B }
#=> [5, 27, 45, 48, 70].all? { |e| e == ?B }
#=> false
Notice that false is returned to each right after:
5 == ?B
#=> false
is evaluated. Since false is returned, we move on to the second element of the enumerator:
[13, 19, 41, 51, 69].all? { |e| e == ?B }
#=> false
so we continue. But
["B", "B", "B", "B", "B"].all? { |e| e == ?B }
#=> true
so when true is returned to each, the latter returns true and we are finished.

Array select to get true and false arrays?

I know I can get this easily:
array = [45, 89, 23, 11, 102, 95]
lower_than_50 = array.select{ |n| n<50}
greater_than_50 = array.select{ |n| !n<50}
But is there a method (or an elegant manner) to get this by only running select once?
[lower_than_50, greater_than_50] = array.split_boolean{ |n| n<50}
over, under_or_equal = [45, 89, 23, 11, 102, 95].partition{|x| x>50 }
Or simply:
result = array.partition{|x| x>50 }
p result #=> [[89, 102, 95], [45, 23, 11]]
if you rather want the result as one array with two sub-arrays.
Edit: As a bonus, here is how you would to it if you have more than two alternatives and want to split the numbers:
my_custom_grouping = -> x do
case x
when 1..50 then :small
when 51..100 then :large
else :unclassified
end
end
p [-1,2,40,70,120].group_by(&my_custom_grouping) #=> {:unclassified=>[-1, 120], :small=>[2, 40], :large=>[70]}
The answer above is spot on!
Here is a general solution for more than two partitions (for example: <20, <50, >=50):
arr = [45, 89, 23, 11, 102, 95]
arr.group_by { |i| i < 20 ? 'a' : i < 50 ? 'b' : 'c' }.sort.map(&:last)
=> [[11], [45, 23], [89, 102, 95]]
This can be very useful if you're grouping by chunks (or any mathematically computable index such as modulo):
arr.group_by { |i| i / 50 }.sort.map(&:last)
=> [[45, 23, 11], [89, 95], [102]]

Every Other 2 Items in Array

I need a ruby formula to create an array of integers. The array must be every other 2 numbers as follows.
[2, 3, 6, 7, 10, 11, 14, 15, 18, 19...]
I have read a lot about how I can do every other number or multiples, but I am not sure of the best way to achieve what I need.
Here's an approach that works on any array.
def every_other_two arr
arr.select.with_index do |_, idx|
idx % 4 > 1
end
end
every_other_two((0...20).to_a) # => [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
# it works on any array
every_other_two %w{one two three four five six} # => ["three", "four"]
array = []
#Change 100000 to whatever is your upper limit
100000.times do |i|
array << i if i%4 > 1
end
This code works for any start number to any end limit
i = 3
j = 19
x =[]
(i...j).each do |y|
x << y if (y-i)%4<2
end
puts x
this should work
For fun, using lazy enumerables (requires Ruby 2.0 or gem enumerable-lazy):
(2..Float::INFINITY).step(4).lazy.map(&:to_i).flat_map { |x| [x, x+1] }.first(8)
#=> => [2, 3, 6, 7, 10, 11, 14, 15]
here's a solution that works with infinite streams:
enum = Enumerator.new do |y|
(2...1/0.0).each_slice(4) do |slice|
slice[0 .. 1].each { |n| y.yield(n) }
end
end
enum.first(10) #=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
enum.each do |n|
puts n
end
Single Liner:
(0..20).to_a.reduce([0,[]]){|(count,arr),ele| arr << ele if count%4 > 1;
[count+1,arr] }.last
Explanation:
Starts the reduce look with 0,[] in count,arr vars
Add current element to array if condition satisfied. Block returns increment and arr for the next iteration.
I agree though that it is not so much of a single liner though and a bit complex looking.
Here's a slightly more general version of Sergio's fine answer
module Enumerable
def every_other(slice=1)
mod = slice*2
res = select.with_index { |_, i| i % mod >= slice }
block_given? ? res.map{|x| yield(x)} : res
end
end
irb> (0...20).every_other
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> (0...20).every_other(2)
=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
irb> (0...20).every_other(3)
=> [3, 4, 5, 9, 10, 11, 15, 16, 17]
irb> (0...20).every_other(5) {|v| v*10 }
=> [50, 60, 70, 80, 90, 150, 160, 170, 180, 190]

How to interleave arrays of different length in Ruby

If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?
Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end
Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]

Resources