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!
Related
I can't get the code to display the information about the people correctly. I'm able to list the names but not the ages.
my_group = ["person_1", "person_2", "person_3"]
person_1 = {name: "erik", gender: "male", age: 26}
person_2 = {name: "erika", gender: "female", age: 26}
person_3 = {name: "erka", gender: "alpha", age: 27}
my_group.each do |name|
my_group.each do |age|
puts "Hello, #{name} is about #{age} years old"
end
puts "Hello, #{name} is about #{age} years old"
end
I don't get an error code, but it does not list the age with the names.
Actually your my_group contains only Strings. If you want to include persons variable you may include them as variables (without ""):
person_1 = {name: "erik", gender: "male", age: 26}
person_2 = {name: "erika", gender: "female", age: 26}
person_3 = {name: "erka", gender: "alpha", age: 27}
my_group = [ person_1, person_2, person_3 ]
Then you can use Array#each method to loop on each person into this array.
my_group.each do |person|
puts "Hello, #{person[:name]} is about #{person[:age]} years old"
end
Also note that each person is a Hash. If you want to access to age property of person_1, for example, you have to do this:
puts person_1[:name]
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.
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}]
I have a array of hashes that have date keys in each hash like this:
data = [
{date: Date.new(2012,1), name: "1"},
{date: Date.new(2012,8), name: "2"},
{date: Date.new(2013,2), name: "3"},
{date: Date.new(2013,6), name: "4"},
{date: Date.new(2013,9), name: "5"},
{date: Date.new(2014,3), name: "6"},
{date: Date.new(2014,4), name: "7"},
{date: Date.new(2014,8), name: "8"},
]
I want to group the hashes by business year, in other words April to next year March like this:
[
[{date: Date.new(2012,1), name: "1"}],
[{date: Date.new(2012,8), name: "2"},
{date: Date.new(2013,2), name: "3"}],
[{date: Date.new(2013,6), name: "4"},
{date: Date.new(2013,9), name: "5"},
{date: Date.new(2014,3), name: "6"}],
[{date: Date.new(2014,4), name: "7"},
{date: Date.new(2014,8), name: "8"}]
]
To accomplish I wrote like this:
result = []
4.times do |i|
result[i] = []
data.each do |datum|
result[i] << datum if datum[:date].between?(Date.new(2011+i,4), Date.new(2012+i,3))
end
end
But this code works only when I know the time frame I'm dealing with.
I feel there is a better way to write it in Ruby more concisely.
How can I write this function?
You could use Enumerable#group_by:
require 'date'
data.group_by { |h| (h[:date].month > 3) ? h[:date].year : h[:date].year - 1 }
#=> {2011=>[{:date=>#<Date: 2012-01-01 ((2455928j,0s,0n),+0s,2299161j)>, :name=>"1"}],
# 2012=>[{:date=>#<Date: 2012-08-01 ((2456141j,0s,0n),+0s,2299161j)>, :name=>"2"},
# {:date=>#<Date: 2013-02-01 ((2456325j,0s,0n),+0s,2299161j)>, :name=>"3"}],
# 2013=>[{:date=>#<Date: 2013-06-01 ((2456445j,0s,0n),+0s,2299161j)>, :name=>"4"},
# {:date=>#<Date: 2013-09-01 ((2456537j,0s,0n),+0s,2299161j)>, :name=>"5"},
# {:date=>#<Date: 2014-03-01 ((2456718j,0s,0n),+0s,2299161j)>, :name=>"6"}],
# 2014=>[{:date=>#<Date: 2014-04-01 ((2456749j,0s,0n),+0s,2299161j)>, :name=>"7"},
# {:date=>#<Date: 2014-08-01 ((2456871j,0s,0n),+0s,2299161j)>, :name=>"8"}]}
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 }