Sorting an array by category names - ruby

Given this array a:
a = [
{id: 3, category_name: "Horror"},
{id: 4, category_name: "Non-Fiction"},
{id: 5, category_name: "LGBT"},
{id: 6, category_name: "Romance"},
{id: 7, category_name: "Romance"}
]
I would like a way to order the objects in the array a by category names using the order of the array (indices), as Ruby arrays luckily are ordered arrays by default:
categories_sorted = [
"Romance",
"LGBT",
"Non-Fiction",
"Horror"
]
So that the sorting algorithm would give me an array that would look like this:
result = [
{id: 6, category_name: "Romance"},
{id: 7, category_name: "Romance"},
{id: 5, category_name: "LGBT"},
{id: 4, category_name: "Non-Fiction"},
{id: 3, category_name: "Horror"}
]
Can you point out any ways to achieve this efficiently?
The array "a" could be as big as 20 objects, and the sorted categories can have as much as 30 categories in my scenario.

do as below :-
a = [
{id: 3, category_name: "Horror"},
{id: 4, category_name: "Non-Fiction"},
{id: 5, category_name: "LGBT"},
{id: 6, category_name: "Romance"},
{id: 7, category_name: "Romance"}
]
categories_sorted = [
"Romance",
"LGBT",
"Non-Fiction",
"Horror"
]
p a.sort_by { |h| [categories_sorted.index(h[:category_name]), h[:id]] }
# >> [{:id=>6, :category_name=>"Romance"}, {:id=>7, :category_name=>"Romance"}, {:id=>5, :category_name=>"LGBT"}, {:id=>4, :category_name=>"Non-Fiction"}, {:id=>3, :category_name=>"Horror"}]

It's worth compiling a sort index list to avoid having to look things up constantly:
categories_sorted_index = Hash[
categories_sorted.each_with_index.collect do |name, index|
[ name, index ]
end
]
a.sort_by do |entry|
categories_sorted_index[entry[:category_name]] || 0
end
# =>
# {:id=>7, :category_name=>"Romance"}
# {:id=>6, :category_name=>"Romance"}
# {:id=>5, :category_name=>"LGBT"}
# {:id=>4, :category_name=>"Non-Fiction"}
# {:id=>3, :category_name=>"Horror"}

Here's a way that uses Enumerable#group_by and Hash#values_at rather than Enumerable#sort_by:
a.group_by { |h| h[:category_name] }.values_at(*categories_sorted).flatten
#=> [{:id=>6, :category_name=>"Romance"},
# {:id=>7, :category_name=>"Romance"},
# {:id=>5, :category_name=>"LGBT"},
# {:id=>4, :category_name=>"Non-Fiction"},
# {:id=>3, :category_name=>"Horror"}]

Related

Convert Array of Hashes to a Hash

I'm trying to convert the following:
dep = [
{id: 1, depen: 2},
{id: 1, depen: 3},
{id: 3, depen: 4},
{id: 5, depen: 3},
{id: 3, depen: 6}
]
Into a single hash:
# {1=>2, 1=>3, 3=>4, 5=3, 3=>6}
I tried a solution I found on another question:
dep.each_with_object({}) { |g,h| h[g[:id]] = g[:dep_id] }
However, the output removed elements and gave me:
#{1=>3, 3=>6, 5=>2}
where the last element is also incorrect.
You cannot have a hash like {1=>2, 1=>3, 3=>4, 5=3, 3=>6}. All keys of a hash mst have be unique.
If you want to get a hash mapping each id to a list of dependencies, you can use:
result = dep.
group_by { |obj| obj[:id] }.
transform_values { |objs| objs.map { |obj| obj[:depen] } }
Or
result = dep.reduce({}) do |memo, val|
memo[val[:id]] ||= []
memo[val[:id]].push val[:depen]
memo
end
which produce
{1=>[2, 3], 3=>[4, 6], 5=>[3]}

Delete duplicate entries in Ruby

What is the command for deleting duplicate elements in an array? This is my best try:
my_array.reject.with_string{s.clone}
If you want an array of unique values of my_array = [1, 2, 3, 3, 4], then do this:
my_array.uniq
# => [1, 2, 3, 4]
If your array contains objects with some field that you want to be unique, for example, :fname in:
my_array = [
{fname: "amanze", age: 28},
{fname: "ben", age: 13},
{fname: "ben", age: 4}
]
then you need to do this:
my_array.uniq { |obj| obj[:fname] }
# =>
# [
# {fname: "amanze", age: 28},
# {fname: "ben", age: 13}
# ]
Array#uniq is the best way to find out the uniq records, but as an alternate, you can use Array#&, which returns a new array containing the elements common to the two arrays, excluding any duplicates.
a = [1, 2, 3, 4, 5, 2, 2, 3, 4]
b = a & a
b #=> [1, 2, 3, 4, 5]

Make the first row as the keys for hash for the next rows?

I am having a hard time figuring out how to make the next rows a hash with the key from the first row.
I have an array structured like this:
[["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
To be:
[{ id : 1, name: "James", address: "..."}, ...]
I used a gem "simple_xlsx_reader", I am extracting out only the first sheet.
wb.sheets.first.row
and got a similar array output from above.
thanks!
arr = [["id", "name"], [1, "Jack"], [2, "Jill"]]
[arr.first].product(arr.drop 1).map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The steps:
b = [arr.first]
#=> [["id", "name"]]
c = arr.drop 1
#=> [[1, "Jack"], [2, "Jill"]]
d = b.product(c)
#=> [[["id", "name"], [1, "Jack"]], [["id", "name"], [2, "Jill"]]]
d.map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The first element of d passed to map's block is:
a = d.first
[["id", "name"], [1, "Jack"]]
The block calculation is therefore:
e = a.transpose
#=> [["id", 1], ["name", "Jack"]]
e.to_h
#=> {"id"=>1, "name"=>"Jack"}
This is what you're looking for:
arr = [["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
keys, *values = arr
values.map {|vals| keys.zip(vals).to_h }
Enumerable#zip takes two arrays (the receiver and the argument) and "zips" them together, producing an array of tuples (two-element arrays) e.g.:
keys = [ "foo", "bar", "baz" ]
values = [ 1, 2, 3 ]
p keys.zip(values)
# => [ [ "foo", 1 ], [ "bar", 2 ], [ "baz", 3 ] ]
Array#to_h takes an array of tuples and turns it into a hash.
If you're using a version of Ruby earlier than 2.1 you'll need to use Hash[ *keys.zip(vals) ] instead.
P.S. If you want symbol keys instead of string keys you'll want to perform that conversion before the map, e.g.:
keys = keys.map(&:to_sym)
Or, if you don't mind modifying the original array:
keys.map!(&:to_sym)
You can try this very simple one line that make your work
arr =[["id", "name", "address"], [1, "James", "add 1"], [2, "John", "add2"] ]
arr.map {|a| arr.first.zip(a).to_h unless a == arr.first }.compact

Ruby sort array of hashes by child-parent relation

So we have and array of hashes
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
target_array = []
What is the most efficient and ruby way to map/sort that array to the following result:
target_array = [
{id:1,children:
[{id: 2, children: [
{id:4, children:[]}]}]},
{id: 3, children:[]}
]
p.s.The most I am capable of is iterating whole thing for each item and excluding from array hash that is already mapped to target_array.
You can solve this with recursion :
#array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
def build_hierarchy target_array, n
#array.select { |h| h[:parent_id] == n }.each do |h|
target_array << {id: h[:id], children: build_hierarchy([], h[:id])}
end
target_array
end
build_hierarchy [], 0
Output :
=> [{"id"=>1, "children"=>[{"id"=>2, "children"=>[{"id"=>4, "children"=>[]}]}]}, {"id"=>3, "children"=>[]}]
Live example in this ruby fiddle http://rubyfiddle.com/riddles/9b643
I would use recursion, but the following could easily be converted to a non-recursive method.
First construct a hash linking parents to their children (p2c). For this, use 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:
#p2c = array.each_with_object({}) { |g,h|
h.update(g[:parent_id]=>[g[:id]]) { |_,ov,nv| ov+nv } }
#=> {0=>[1, 3], 1=>[2], 2=>[4]}
There are many other ways to construct this hash. Here's another:
#p2c = Hash[array.group_by { |h| h[:parent_id] }
.map { |k,v| [k, v.map { |g| g[:id] }] }]
Now construct a recursive method whose lone argument is a parent:
def family_tree(p=0)
return [{ id: p, children: [] }] unless #p2c.key?(p)
#p2c[p].each_with_object([]) { |c,a|
a << { id:c, children: family_tree(c) } }
end
We obtain:
family_tree
#=> [ { :id=>1, :children=>
# [
# { :id=>2, :children=>
# [
# { :id=>4, :children=>[] }
# ]
# }
# ]
# },
# { :id=>3, :children=>[] }
# ]
Constructing the hash #p2c initially should make it quite efficient.
This is what I tried my way using Hash
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
target_hash = Hash.new { |h,k| h[k] = { id: nil, children: [ ] } }
array.each do |n|
id, parent_id = n.values_at(:id, :parent_id)
target_hash[id][:id] = n[:id]
target_hash[parent_id][:children].push(target_hash[id])
end
puts target_hash[0]
Output:
{:id=>nil, :children=>[{:id=>1, :children=>[{:id=>2, :children=>[{:id=>4, :children=>[]}]}]}, {:id=>3, :children=>[]}]}
I think the best one will have O(nlog(n)) time complexity at most. I'm giving my non-hash one :
array = [
{id: 1, parent_id: 0},
{id: 2, parent_id: 1},
{id: 3, parent_id: 0},
{id: 4, parent_id: 2}
]
# This takes O(nlog(n)).
array.sort! do |a, b|
k = (b[:parent_id] <=> b[:parent_id])
k == 0 ? b[:id] <=> a[:id] : k
end
# This takes O(n)
target_array = array.map do |node|
{ id: node[:id], children: [] }
end
# This takes O(nlog(n))
target_array.each_with_index do |node, index|
parent = target_array[index + 1...target_array.size].bsearch do |target_node|
target_node[:id] == array[index][:parent_id]
end
if parent
parent[:children] << node
target_array[index] = nil
end
end
# O(n)
target_array.reverse.compact
# =>
# [{:id => 1, :children =>[{:id=>2,:children=> [ {:id=>4,
# :children=>[]}]}]},
# {:id=>3, :children=>[]} ]
So mine uses O(nlog(n)) in general.
By the way, when I simply tested out the existing solutions I found Gagan Gami's to be most efficient (slightly ahead of mine), I believe it's O(nlog(n)) too, though not obvious. But the currently accepted solution takes O(n^2) time.

Clever Way To Get 2nd Item From Each Group In Array Of Hashes?

The problem would be easy to solve with a manual loop and a result array, which you add onto as you go. But I'm looking for a more ruby-esque solution, probably something that uses inject or select. Here's the problem:
arr_of_hashes = [
{id: 1, val: "blah1"},
{id: 1, val: "blah2"},
{id: 1, val: "blah3"},
{id: 2, val: "blah4"},
{id: 2, val: "blah5"},
{id: 3, val: "blah6"},
{id: 3, val: "blah7"},
{id: 3, val: "blah8"},
{id: 3, val: "blah9"}
]
"Groups" are defined by the "id" field in the hashes. We are guaranteed each group has at least two items. We want to return an array containing the 2nd item of each group:
output_should_be = [
{id: 1, val: "blah2"},
{id: 2, val: "blah5"},
{id: 3, val: "blah7"}
]
"I'm looking for a more ruby-esque solution". I guess you mean functional:
arr_of_hashes.chunk { |h| h[:id] }.map { |id, hs| hs[1] }
#=> [{:id=>1, :val=>"blah2"}, {:id=>2, :val=>"blah5"}, {:id=>3, :val=>"blah7"}]
Use group_by if elements are not pre-ordered by id.
Here's a solution using group_by:
arr_of_hashes.group_by{|h| h[:id]}.to_a.map{|id,a| a[1]}

Resources