Find set of objects in array that have same attributes - ruby

Given that I have an array with two attributes: 'n_parents' and 'class', which looks like this:
my_arr = [{n_parents: 10, class: 'right'}, {n_parents: 10, class: 'right'}, {n_parents: 5, class: 'left'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
I would like to get an array with the objects that share most of those two attributes. So in the previous example:
result = [{n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
Because there are three objects that share n_parents = 2, and class = 'center'.
So far, I know how can I group by dos two attributes, but after that I am not sure how to get the set that has more elements.
Right now I have:
my_arr.group_by { |x| [x[:n_parents], x[:class]] }

This should work for you. It groups the hashes by the hash itself and then gets the largest group by the array count
my_arr = [{n_parents: 10, class: 'right'}, {n_parents: 10, class: 'right'}, {n_parents: 5, class: 'left'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
my_arr.group_by { |h| h }.max_by { |h,v| v.count }.last
#=>[{:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}]

something like below :
my_arr.group_by(&:values).max_by { |_,v| v.size }.last
# => [{:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"}]

I am using code used by OP and extending over that to get result he wants:--
my_arr.group_by { |x| [x[:n_parents], x[:class]] }.max_by{|k,v| v.size}.last
Output
#=> [{:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}]

This is the fourth answer to be posted. The three earlier answers all employed group_by/max_by/last. Sure, that may be the best approach, but is it the most interesting, the most fun? Here are a couple other ways to generate the desired result. When
my_arr = [{n_parents: 10, class: 'right' }, {n_parents: 10, class: 'right' },
{n_parents: 5, class: 'left' }, {n_parents: 2, class: 'center'},
{n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
the desired result is:
#=> [{:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"}]
#1
# Create a hash `g` whose keys are the elements of `my_arr` (hashes)
# and whose values are counts for the elements of `my_arr`.
# `max_by` the values (counts) and construct the array.
el, nbr = my_arr.each_with_object({}) { |h,g| g[h] = (g[h] ||= 0) + 1 }
.max_by { |_,v| v }
arr = [el]*nbr
#2
# Sequentially delete the elements equal to the first element of `arr`,
# each time calculating the number of elements deleted, by determining
# `arr.size` before and after the deletion. Compare that number with the
# largest number deleted so far to find the element with the maximum
# number of instances in `arr`, then construct the array.
arr = my_arr.map(&:dup)
most_plentiful = { nbr_copies: 0, element: [] }
until arr.empty? do
sz = arr.size
element = arr.delete(arr.first)
if sz - arr.size > most_plentiful[:nbr_copies]
most_plentiful = { nbr_copies: sz - arr.size, element: element }
end
end
arr = [most_plentiful[:element]]* most_plentiful[:nbr_copies]

Related

How to get the most common class among elements

How can I get the most common data type (i.e. class) among the elements of an array? For example, for this array:
array = [nil, "string", 1, 3, 0.234, 25, "hot potato"]
Integer should be returned since it's the most common class.
array.group_by(&:class).max_by{|k, v| v.length}.first
# => Integer
array.each_with_object(Hash.new(0)) { |e,h| h[e.class] += 1 }.
max_by(&:last).
first
#=> Integer
the first step being
array.each_with_object(Hash.new(0)) { |e,h| h[e.class] += 1 }
#=> {NilClass=>1, String=>2, Integer=>3, Float=>1}
Following can also work,
array.inject(Hash.new(0)) { |h,v| h[v.class] += 1; h }.max_by(&:last).first

Any instance of key anywhere in a nested hash

Given a hash like so:
h = {
"actual_amount" => 20,
"otherkey" => "value",
"otherkey2" => [{"actual_amount" => 30, "random_amount" => 45}]
}
where there are any number of layers of nesting, is there a simple way to pluck all the key-value pairs (or just the values) of the keys that are actual_amount?
I've assumed the values of keys are literals or arrays of hashes.
This question clearly calls for a recursive solution.
def amounts(h)
h.each_with_object([]) do |(k,v),a|
case v
when Array
v.each { |g| a.concat amounts(g) }
else
a << v if k == "actual_amount"
end
end
end
Suppose
h = {
"actual_amount"=>20,
1=>2,
2=>[
{ "actual_amount"=>30,
3=>[
{ "actual_amount" => 40 },
{ 4=>5 }
]
},
{ 5=>6 }
]
}
then
amounts(h)
#=> [20, 30, 40]
Using the hash, provided by Cary, as an input:
▶ flatten = ->(inp) do
▷ [*(inp.respond_to?(:map) ? inp.map(&:flatten) : inp)]
▷ end
▶ res = flatten(h).first
▶ res.select.with_index do |_, i|
▷ i > 0 && res[i - 1] == 'actual_amount'
▷ end
#⇒ [20, 30, 40]

Frequency of pairs in an array ruby

I have an array of pairs like this:
arr = [
{lat: 44.456, lng: 33.222},
{lat: 42.456, lng: 31.222},
{lat: 44.456, lng: 33.222},
{lat: 44.456, lng: 33.222},
{lat: 42.456, lng: 31.222}
]
There are some geographical coordinates of some places. I want to get an array with these coordinates grouped and sorted by frequency. The result should look like this:
[
{h: {lat: 44.456, lng: 33.222}, fr: 3},
{h: {lat: 42.456, lng: 31.222}, fr: 2},
]
How can I do this?
The standard ways of approaching this problem are to use Enumerable#group_by or a counting hash. As others have posted answers using the former, I'll go with the latter.
arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }.map { |k,v| { h: k, fr: v } }
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]
First, count instances of the hashes:
counts = arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }
#=> {{:lat=>44.456, :lng=>33.222}=>3,
# {:lat=>42.456, :lng=>31.222}=>2}
Then construct the array of hashes:
counts.map { |k,v| { h: k, fr: v } }
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]
g = Hash.new(0) creates an empty hash with a default value of zero. That means that if g does not have a key k, g[k] returns zero. (The hash is not altered.) g[k] += 1 is first expanded to g[k] = g[k] + 1. If g does not have a key k, g[k] on the right side returns zero, so the expression becomes:
g[k] = 1.
Alternatively, you could write:
counts = arr.each_with_object({}) { |f,g| g[f] = (g[f] ||= 0) + 1 }
If you want the elements (hashes) of the array returned to be in decreasing order of the value of :fr (here it's coincidental), tack on Enumerable#sort_by:
arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }.
map { |k,v| { h: k, fr: v } }.
sort_by { |h| -h[:fr] }
arr.group_by(&:itself).map{|k, v| {h: k, fr: v.length}}.sort_by{|h| h[:fr]}.reverse
# =>
# [
# {:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}
# ]
arr.group_by{|i| i.hash}.map{|k, v| {h: v[0], fr: v.size}
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3}, {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]

Optimize grouping array of hashes by date

Given array
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
I want to group by date and output an array of hashes
ouput = [
{date: 2014-01-01, a: 5, b:1, xyz: 11 },
{date: 2014-10-10, qbz: 5, v: 4, q: 1, strpm: -99},
]
I am solving it in Ruby and have a solution but it seems inefficient.
def arrayt(inputarray)
outputarray=[];
inputarray.each do |x|
tindex = includes(outputarray,x[:date])
if tindex == -1
outputarray.push(x)
else
outputarray[tindex].merge!(x)
end
end
return outputarray
end
def includes(array,date)
array.each_with_index do |temp,index|
if date==temp[:date]
return index
end
end
return -1
end
Any help with a more elegant solution would be appreciated
[
{date: '2014-01-01', a: 5, b:1},
{date: '2014-01-01', xyz: 11},
{date: '2014-10-10', qbz: 5},
{date: '2014-10-10', v: 4, q: 1, strpm: -99}
]
.group_by(&:first).map{|_, v| v.inject(:merge)}
Here's a way that employs the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are present in both hashes being merged:
arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }.values
To wit:
hash = arr.each_with_object({}) { |g,h|
h.update({ g[:date]=>g }) { |_,ov,nv| ov.merge(nv) } }
#=>{"2014-01-01"=>{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# "2014-10-10"=>{:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}}
hash.values
#=> [{:date=>"2014-01-01", :a=>5, :b=>1, :xyz=>11},
# {:date=>"2014-10-10", :qbz=>5, :v=>4, :q=>1, :strpm=>-99}]

Converting a array to an array of ranges in Ruby

I have an array of numbers. I want to convert it to an array of ranges. Example:
input = [0,10,20,30]
output = [0..10, 10..20, 20..30, 30..Infinity]
Is there some direct way to do it in Ruby?
Yes, possible :
input = [0,10,20,30]
(input + [Float::INFINITY]).each_cons(2).map { |a,b| a..b }
# => [0..10, 10..20, 20..30, 30..Infinity]
One way:
Code
output = input.zip(input[1..-1] << 1.0/0).map { |r| Range.new(*r) }
Explanation
input = [0,10,20,30]
a = input[1..-1]
#=> [10, 20, 30]
b = a << 1.0/0
#=> [10, 20, 30, Infinity]
c = input.zip(b)
#=> [[0, 10], [10, 20], [20, 30], [30, Infinity]]
output = c.map { |r| Range.new(*r) }
#=> [0..10, 10..20, 20..30, 30..Infinity]
Possible alternative
If you instead wanted an array of arrays, you would just change the block:
output = input.zip(input[1..-1] << 1.0/0).map { |f,l| [f..l] }
#=> [[0..10], [10..20], [20..30], [30..Infinity]]

Resources