Sort an array of objects in ruby - ruby

First of all full disclosure. I attempted to solve this issue at this thread but I wanted to repost in hopes of getting some help in Ruby:
best way to sort an array of objects by category and infinite subcategory
I have made a feeble attempt a that solution but can't even get close and it's not even worth showing. Below is my attempt. It would mean the world to me if someone could give some good direction on this.
data = [{id: 1, name: "parent test 1", parent_id: nil, top_level_category_id: nil},
{id: 2, name: "test 2", parent_id: 1, top_level_category_id: 1},
{id: 3, name: "test 3", parent_id: 1, top_level_category_id: 1},
{id: 4, name: "parent test 4", parent_id: nil, top_level_category_id: nil},
{id: 5, name: "test 5", parent_id: 3, top_level_category_id: 4},
{id: 6, name: "test 6", parent_id: 4, top_level_category_id: 4},
{id: 7, name: "test 7", parent_id: 4, top_level_category_id: 4}]
This is what I am hoping to accomplish
parent test 1
test 2
test 3
test 5
parent test 2
test 6
test 7
-
ord_cat = {}
for item in data
if item[:parent_id] == nil
ord_cat[item[:id]] = {:name => item[:name], :children => {}}
end
end
# fill child directories
for item in data
if item[:parent_id] != nil
ord_cat[item[:top_level_category_id]][:children].merge!({item[:id] => item[:name]})
end
end
puts ord_cat
This is the output
{1=>{:name=>"parent test 1", :children=>{2=>"test 2", 3=>"test 3", 5=>"test 5"}}, 4=>{:name=>"parent test 4", :children=>{6=>"test 6", 7=>"test 7"}}}
This is clearly not nesting "test 5" properly. I'm not thrilled with the structure of the object.

We can construct the ancestor list for each node and sorting based on that list, in effect giving a depth-first traversal of the tree.
A bit lengthy solution, but would solve the problem I believe. Have used hash more to avoid scanning the array multiple times.
data = [{id: 1, name: "parent test 1", parent_id: nil, top_level_category_id: nil},
{id: 2, name: "test 2", parent_id: 1, top_level_category_id: 1},
{id: 3, name: "test 3", parent_id: 1, top_level_category_id: 1},
{id: 4, name: "parent test 4", parent_id: nil, top_level_category_id: nil},
{id: 5, name: "test 5", parent_id: 3, top_level_category_id: 4},
{id: 6, name: "test 6", parent_id: 4, top_level_category_id: 4},
{id: 7, name: "test 7", parent_id: 4, top_level_category_id: 4}]
# Amount of indentation for printing each node's nesting
INDENTATION_PER_LEVEL = 1
id_to_data_map = {}
id_to_parent_id_map = {}
data.each { |d|
id_to_data_map[d[:id]] = d
id_to_parent_id_map[d[:id]] = d[:parent_id]
}
data_with_ancestors = {}
data.each do |record|
ancestors = [record[:name]]
# Temporary parent
parent = record
while true do
parent_id = id_to_parent_id_map[parent[:id]]
break if parent_id.nil? # Hit the root - get out.
parent = id_to_data_map[parent_id]
ancestors << parent[:name]
end
# Construct a list of ancestors for the node, with the oldest ancestor first.
data_with_ancestors[record[:name]] = ancestors.reverse
end
# Sort the flattened list based on the ancestor string constructed by joining all the parent names.
sorted_list = data_with_ancestors.sort_by {|name, ancestors| ancestors.join(" ")}
# Add indentation for the record names based on their nesting in the tree.
print_info = sorted_list.collect {|name, ancestors| (" " * (ancestors.size - 1) * INDENTATION_PER_LEVEL) + name}
print_info.each { |record| puts record }

Related

I receive a (TypeError) when I try to access a nested array of hashes

Let's say I work for a rock climbing gym and I am trying to make the attendance process easier. Here I am trying to access the age values within hashes that are in an array nested in a hash. Though I receive the proper output number of "belayers", I also get a TypeError for the line parties[:attendance].each do |kid|. I understand that this is wrong but I'm unsure how to fix it. Any advice would be useful.
def kids_hash
kids_hash = {
:party_one =>{
:facilitator => 'Erica',
:attendance => [
{name: 'Harry', age: 6, wavers: 'yes', harness: "red", shoe_size: 3},
{name: 'Frankie', age: 9, wavers: 'yes', harness: "blue", shoe_size: 7},
{name: 'Gale', age: 4, wavers: 'yes', harness: "red", shoe_size: 3},
{name: 'Rony', age: 4, wavers: 'no', harness: "red", shoe_size: 2},
{name: 'Julia', age: 10, wavers: 'yes', harness: "blue", shoe_size: 9},
{name: 'Sarah', age: 3, wavers: 'no', harness: "red", shoe_size: 13},
{name: 'James', age: 3, wavers: 'yes', harness: "red", shoe_size: 2},
{name: 'Kevin', age: 5, wavers: 'yes', harness: "red", shoe_size: 3},
{name: 'Jessie', age: 11, wavers: 'yes', harness: "blue", shoe_size: 10}
]
},
:party_two => "not booked yet"
}
end
def num_belayers
kid_count = 0
baby_count= 0
kids_hash.values.each do |parties|
parties[:attendance].each do |kid|
if kid[:age] >= 5
kid_count += 1
else
baby_count +=1
end
end
#if the kids are 5 y/o, we put 5 to a group
belays_kids = kid_count / 5.00
#if they are younger, there are 3 to a group
belays_babies = baby_count / 3.00
belays = belays_kids.ceil + belays_babies.ceil
puts "You will need #{belays} belayers."
end
end
party_two key is not referenced to a Hash so this is why you get exception. Try the following code.
def num_belayers
kid_count = 0
baby_count= 0
kids_hash.each_value do |parties|
next unless parties.is_a?(Hash)
parties[:attendance].each do |kid|
if kid[:age] >= 5
kid_count += 1
else
baby_count +=1
end
end
#if the kids are 5 y/o, we put 5 to a group
belays_kids = kid_count / 5.00
#if they are younger, there are 3 to a group
belays_babies = baby_count / 3.00
belays = belays_kids.ceil + belays_babies.ceil
puts "You will need #{belays} belayers."
end
nil
end
This code throws error when "parties" is not a hash. You are assuming it to be a hash and wrote the code for the same. Thus it ends up throwing exception when something which is not excepted arrives just like in your test inputs party_one is a hash while party_two is a string. I suggest check if "parties" is a hash and proceed else skip the code for that party.

Find & Replace elements in array of hashes - Ruby [duplicate]

This question already has an answer here:
Find and replace specific hash and it's values within array
(1 answer)
Closed 4 years ago.
I have an array of hashes as below
status_arr = [{id: 5, status: false},
{id: 7, status: false},
{id: 3, status: false},
{id: 9, status: false} ]
I would like to update the hash with status: true if it has ids 5, 7
update_ids = [5, 9]
I am trying the following and has no idea to proceed
status_arr.select{ |arr| update_ids.include?(arr[:id]) arr[:status] = true}
Expected output:
status_arr = [{id: 5, status: true},
{id: 7, status: false},
{id: 3, status: false},
{id: 9, status: true} ]
require 'set'
update_ids = Set.new([5,3])
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]
instead of Set you can use just a Hash
update_ids = {5 => true, 3=> true}
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]
Or an array, but it will have some performance issues for big arrays
update_ids = [5,3]
status_arr.map{ |s| s[:status] = update_ids.include?(s[:id]); s }
#=> [{:id=>5, :status=>true}, {:id=>7, :status=>false}, {:id=>3, :status=>true}, {:id=>9, :status=>false}]

Check if any of array hash objects has needed value

I have an array like this:
arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
I need to check if any of arr objects have name Sam. What is the most elegant way? I can only think of cycling with each.
I need to check if any of arr objects have name Sam
Enumerable#any? is a good way to go.
arr = [ {id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
arr.any? {|h| h[:name] == "Sam"}
# => true
Now if you also want to see which Array object has the value Sam in it,you can use Enumerable#find for the same:
arr.find {|h| h[:name] == "Sam"}
# => {:id=>2, :name=>"Sam"}
You can also choose select or count methods
Enumberable#select
> arr = [{id: 1, name: 'John' }, {id: 2, name: 'Sam' }, {id: 3, name: 'Bob' }]
> arr.select { | h | h[:name] == 'Sam' }
# => [{:id=>2, :name=>"Sam"}]
Enumberable#count
> arr.count { | h | h[:name] == 'Sam' }
# => 1
You can use Enumberable#find_all to return all object that match the constrain
arr = [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'},{:id=>3,:first_name=>'samanderson'},{:id=>4,:first_name=>'samuel'}]
arr.find_all{|obj| obj.first_name == 'sam'}
# => [{:id=>1,:first_name=>'sam'},{:id=>2,:first_name=>'sam'}]

How to sort array of hashes by value?

I'm trying to sort an array of hashes based on the given key-value, and will return that value on top of the array first, then preceded by the remaining data.
Example would be:
students = [{name: "John Doe", age: 16, adviser: "Mrs. Robinson"},
{name: "John Smith", age: 18, adviser: "Mrs. Williams"},
{name: "Michael Rodriguez", age: 17, adviser: "Mr. Lee"}]
def sort_by_adviser(data, name)
...
end
> sort_by_adviser(students, "Mr. Lee")
=> [{name: "Michael Rodriguez", age: 17, adviser: "Mr. Lee"},
{name: "John Doe", age: 16, adviser: "Mrs. Robinson"},
{name: "John Smith", age: 18, adviser: "Mrs. Williams"}]
> sort_by_adviser(students, "Mrs. Williams")
=> [{name: "John Smith", age: 18, adviser: "Mrs. Williams"},
{name: "Michael Rodriguez", age: 17, adviser: "Mr. Lee"},
{name: "John Doe", age: 16, adviser: "Mrs. Robinson"}]
Here the output brings the adviser's name on top of the list, then preceded by other hashes in the array.
> sort_by_keyvalue(data, "Z")
=> [{letter: 'Z'},
{letter: 'A'},
.
.
.
{letter: 'Y'}]
> sort_by_keyvalue(data, 5)
=> [{number: 5, value: 'value1'},
{number: 5, value: 'value2'},
{number: 5, value: 'value3'},
{number: 9, value: 'value1'},
{number: 9, value: 'value2'},
{number: 8, value: 'value1'},
{number: 8, value: 'value2'},
{number: 7, value: 'value1'},
{number: 6, value: 'value1'},
{number: 4, value: 'value1'},
{number: 3, value: 'value1'},
{number: 2, value: 'value1'},
{number: 1, value: 'value1'},
{number: 1, value: 'value2'},
{number: 0, value: 'value1'}]
Anyone knows how to do it?
Another implemention :)
def sort_by_adviser(data, name)
data.each_with_index do |hash,index|
if hash[:adviser]==name
data.delete_at index #delete from array
data.unshift hash
break
end
end
data
end
> sort_by_adviser(students, "Mr. Lee")
#=> [{:name=>"Michael Rodriguez", :age=>17, :adviser=>"Mr. Lee"}, {:name=>"John Doe", :age=>16, :adviser=>"Mrs. Robinson"}, {:name=>"John Smith", :age=>18, :adviser=>"Mrs. Williams"}]
def creamy_sort(key, value, arr)
top, bottom = arr.partition{|e| e[key] == value }
top.concat(bottom.sort{|a,b| b[key] <=> a[key]})
end
creamy_sort(:adviser, "Mr. Lee", students)
You can do that:
def sort_by_adviser(data, name)
data = data.sort{|x,y|x[:adviser] <=> y[:adviser]}
i = data.index{|h|h[:adviser] = name}
h = data.delete_at i
data.unshift h
end
I have this solution:
students = [{name: "John Doe", age: 16, adviser: "Mrs. Robinson"},
{name: "John Smith", age: 18, adviser: "Mrs. Williams"},
{name: "Michael Rodriguez", age: 17, adviser: "Mr. Lee"}]
def sort_by_adviser(data, *name)
data.sort_by{| entry |
[
name.index(entry[:adviser]) || 999,
entry[:age], entry[:name] #2nd sort criteria
]
}
end
p sort_by_adviser(students, "Mr. Lee")
#[{:name=>"Michael Rodriguez", :age=>17, :adviser=>"Mr. Lee"}, {:name=>"John Doe", :age=>16, :adviser=>"Mrs. Robinson"}, {:name=>"John Smith", :age=>18, :adviser=>"Mrs. Williams"}]
p sort_by_adviser(students, "Mrs. Williams")
# [{:name=>"John Smith", :age=>18, :adviser=>"Mrs. Williams"}, {:name=>"John Doe", :age=>16, :adviser=>"Mrs. Robinson"}, {:name=>"Michael Rodriguez", :age=>17, :adviser=>"Mr. Lee"}]
I didn't understand, what's the sorting of the remaining entry.
You wrote: then preceded by the remaining data. What's tho order criteria of the hash?
I selected age, followed by name. But you may adapt it for your need.
def weird_sort(array, key, value)
return array.sort_by{|d| 2 <=> (d[key] == value).object_id}
end
This is based on the fact that true.object_id equals 2 in ruby.
Kind of a weird solution that's why it's a weird_sort :p It also messes around the other value ordering ... so it only guarantees you that values that are equal go on top!

Remove element from array

I have an array in my Rails 3.1 apps that has made by several objects:
[#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>, #<Hardware id: 19, brand_id: 9, model: "JetLab", description: "JetLab - 600 ", typology_id: 5, sub_typology_id: nil, created_at: nil, updated_at: nil>]
and I want remove one object from this array. Using Rails console, I've tried to do something such as (try to remove first object):
array.pop=#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>
but it doesn't work. How can I do this?
UPDATED: My goal isn't to pop last element on array, but a generic object (everywhere inside array) that I should find using mysql search query.
my_array = [ 1, 2, 3 ]
item = my_array.pop
puts item
# => 3
puts my_array
# => [ 1, 2 ]
You probably want to use the Array#delete function
an_array = [1,3,4]
an_array.delete(3)
# => 3
puts an_array
# => [1,4]
Check it out in the Ruby documentation:
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-delete

Resources