I'm trying to create a new array of hashes with unique values and with respecting the highest version of repeated hashes.
The hash looks like the following:
old_hash = [
{"dependency"=>"websocket", "version"=>"2.8.0", "repo"=>"repo1"},
{"dependency"=>"rails", "version"=>"6.2.0", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.0.3.5", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.1.0.2", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
{"dependency"=>"rails", "version"=>"6.1.0", "repo"=>"repo3"},
{"dependency"=>"metasploit", "version"=>"2.8.0", "repo"=>"repo3"}
]
As you can see, the third, fourth, and fifth hash has the same value of key dependency which is httparty and also repo which is repo2, but the fifth hash has the highest version of these three. Therefore, I'd like to create a unique hash that has the first, second, fifth, sixth, and seventh hash. So the result I'm trying to have should look like this:
unique_hash = [
{"dependency"=>"websocket", "version"=>"2.8.0", "repo"=>"repo1"},
{"dependency"=>"rails", "version"=>"6.2.0", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
{"dependency"=>"rails", "version"=>"6.1.0", "repo"=>"repo3"},
{"dependency"=>"metasploit", "version"=>"2.8.0", "repo"=>"repo3"}
]
Regarding the version comparison, I'm thinking of using this method to compare them right:
def version_greater? (version1, version2)
Gem::Version.new(version1) > Gem::Version.new(version2)
end
which returns true in case version1 is greater than version2.
I would appreciate any suggestions that helps to solve this problem.
One approach is to use the form of Hash#update (aka merge!) that takes a block (here { |_,o,n| n["version"] > o["version"] ? n : o }) to determine the values of keys that are present in both hashes being merged.
old_hash = [
{"dependency"=>"websocket", "version"=>"2.8.0", "repo"=>"repo1"},
{"dependency"=>"rails", "version"=>"6.2.0", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.0.3.5", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.1.0.2", "repo"=>"repo2"},
{"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
{"dependency"=>"rails", "version"=>"6.1.0", "repo"=>"repo3"},
{"dependency"=>"metasploit", "version"=>"2.8.0", "repo"=>"repo3"},
{"dependency"=>"rails", "version"=>"6.1.9", "repo"=>"repo2"}
]
Note that I've added a hash to old_hash shown in the question. (Incidentally, "old_hash" is perhaps not the best name for an array.)
old_hash.each_with_object({}) do |g,h|
h.update([g["dependency"],g["repo"]]=>g) do |_,o,n|
n["version"] > o["version"] ? n : o
end
end.values
#=> [{"dependency"=>"websocket", "version"=>"2.8.0", "repo"=>"repo1"},
# {"dependency"=>"rails", "version"=>"6.2.0", "repo"=>"repo2"},
# {"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
# {"dependency"=>"rails", "version"=>"6.1.0", "repo"=>"repo3"},
# {"dependency"=>"metasploit", "version"=>"2.8.0", "repo"=>"repo3"}]
The receiver of values can be seen to be the following.
{["websocket", "repo1"] =>{"dependency"=>"websocket", "version"=> "2.8.0", "repo"=>"repo1"},
["rails", "repo2"] =>{"dependency"=>"rails", "version"=> "6.2.0", "repo"=>"repo2"},
["httparty", "repo2"] =>{"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
["rails", "repo3"] =>{"dependency"=>"rails", "version"=> "6.1.0", "repo"=>"repo3"},
["metasploit", "repo3"]=>{"dependency"=>"metasploit", "version"=> "2.8.0", "repo"=>"repo3"}}
Consult the doc for descriptions of the three block variables: _ (the common key, here an underscore to signal that it is not used in the block calculation), o, the value of the common key in the hash being constructed (think "old"), and n, the value of the common key in the hash being merged (think "new").
The problem was solved by using:
old_hash.group_by {|h| h.values_at("dependency","repo")}.map {|_,v| v.max_by {|h| Gem::Version.new(h["version"])}}
Thanks to #engineersmnky.
Sort By Semantic Version, Then by Select by Gem Name
The easiest and most readable (but not necessarily shortest or fastest) way to sort your items that I can think of is:
sorted_array = old_hash.sort_by { Gem::Version.new _1["version"] }
gems = old_hash.map { _1["dependency"] }.uniq.sort
gems.map { |gem| sorted_array.select { _1["dependency"] == gem }.last }
In Ruby 3.0.2, this yields:
[{"dependency"=>"httparty", "version"=>"6.1.3.2", "repo"=>"repo2"},
{"dependency"=>"metasploit", "version"=>"2.8.0", "repo"=>"repo3"},
{"dependency"=>"rails", "version"=>"6.2.0", "repo"=>"repo2"},
{"dependency"=>"websocket", "version"=>"2.8.0", "repo"=>"repo1"}]
Basically, you sort your Array of Hashes by the semantic version, and then rely on your Array's sorted order and the fact that the last Hash for each gem "wins" (because of the duplicate dependency keys) to remove older items.
As a bonus, the gem names also appear in sorted order in your new Array. This makes it a bit easier to visually scan through them, especially as the list gets longer.
Related
i have a matrix like this
[
["name", "company1", "company2", "company3"],
["hr_admin", "Tom", "Joane", "Kris"],
["manager", "Philip", "Daemon", "Kristy"]
]
How can I convert into this data structure?
{
"company1" => {
"hr_admin"=> "Tom",
"manager" => "Philip"
},
"Company2" => {
"hr_admin"=> "Joane",
"manager" => "Daemon"
},
"company3" => {
"hr_admin"=> "Kris",
"manager" => "Kristy"
}
}
I have tried approach like taking out the first row of matrix (header) and zipping the rest o
f the matrix to change their position. It worked to some extent but it doesnt looks very good. So I am turning up here for help.
matrix[0][1...matrix[0].length].each_with_index.map do |x,i|
values = matrix[1..matrix.length].map do |x|
[x[0], x[i+1]]
end.to_h
[x, values]
end.to_h
matrix[0].length and matrix.length could be omittable depending on ruby version.
First you take all elements of first row but first.
then you map them with index to e.g. [["hr_admin", "Tom"],["manager", "Phil"]] using the index
then you call to_h on every element and on whole array.
arr = [
["name", "company1", "company2", "company3"],
["hr_admin", "Tom", "Joane", "Kris"],
["manager", "Philip", "Daemon", "Kristy"]
]
Each key-value pair of the hash to be constructed is formed from the "columns" of arr. It therefore is convenient to compute the transpose of arr:
(_, *positions), *by_company = arr.transpose
#=> [["name", "hr_admin", "manager"],
# ["company1", "Tom", "Philip"],
# ["company2", "Joane", "Daemon"],
# ["company3", "Kris", "Kristy"]]
I made use of Ruby's array decomposition (a.k.a array destructuring) feature (see this blog for elabortion) to assign different parts of the inverse of arr to variables. Those values are as follows1.
_ #=> "name"
positions
#=> ["hr_admin", "manager"]
by_company
#=> [["company1", "Tom", "Philip"],
# ["company2", "Joane", "Daemon"],
# ["company3", "Kris", "Kristy"]]
It is now a simple matter to form the desired hash. Once again I will use array decomposition to advantage.
by_company.each_with_object({}) do |(company_name, *employees),h|
h[company_name] = positions.zip(employees).to_h
end
#=> {"company1"=>{"hr_admin"=>"Tom", "manager"=>"Philip"},
# "company2"=>{"hr_admin"=>"Joane", "manager"=>"Daemon"},
# "company3"=>{"hr_admin"=>"Kris", "manager"=>"Kristy"}}
When, for example,
company_name, *employees = ["company1", "Tom", "Philip"]
company_name
#=> "company1"
employees
#=> ["Tom", "Philip"]
so
h[company_name] = positions.zip(employees).to_h
h["company1"] = ["hr_admin", "manager"].zip(["Tom", "Philip"]).to_h
= [["hr_admin", "Tom"], ["manager", "Philip"]].to_h
= {"hr_admin"=>"Tom", "manager"=>"Philip"}
Note that these calculations do not depend on the numbers of rows or columns of arr.
1. As is common practice, I used the special variable _ to signal to the reader that its value is not used in subsequent calculations.
I'm trying to sort a hash within a hash. I'd like to sort the hash by the sub-key. I'm using ruby.
I've tried using the sort_by method and iterating the over the hash to reorganize the sub-key. I received the error "ArgumentError: comparison of Hash with Hash failed"
hash = {2012 => {"regularseason" => [game_1, game_2, game_3],
"post_season" => [game_4, game_5]
},
2013 => {"regularseason" => [game_6, game_7, game_8],
"post_season" => [game_9, game_10]
},
2014 => {"post_season" => [game_11, game_12, game_13],
"regularseason" => [game_14, game_15]
}
}
Desired Result:
I would like to sort this hash so sub-key post_season will always appear before sub-key regularseason.
Use Hash#transform_values to sort values:
hash.transform_values { |v| v.sort.to_h }
#⇒ {2012=>{"post_season"=>[:game_4, :game_5],
# "regularseason"=>[:game_1, :game_2, :game_3]},
# 2013=>{"post_season"=>[:game_9, :game_10],
# "regularseason"=>[:game_6, :game_7, :game_8]},
# 2014=>{"post_season"=>[:game_11, :game_12, :game_13],
# "regularseason"=>[:game_14, :game_15]}}
Hashes return keys in the order they are inserted so I believe you'll essentially need to rewrite the nested hash.
For example:
hash.each { |(key, nested_hash)| hash[key] = nested_hash.sort.to_h }
This is rather inefficient, however, so you'd be better to see if you can ensure they are always entered in that order or somehow extract them in the order you desire.
Given a hash with keys that include k, we can return a new hash with the same key/value pairs, with k being the first key inserted, and the remaining keys maintaining their original relative order, as follows:
def reorder_key(h, key)
{ key=>h[key] }.merge h
end
For example:
h = { 1=>2, :a=>:b, "c"=>"d" }
reorder_key(h, :a)
#=> {:a=>:b, 1=>2, "c"=>"d"}
We can use this method to obtain the desired hash in the present problem.
hash.transform_values { |h| reorder_key(h, "post_season") }
#=> {2012=>{"post_season" =>[:game_4, :game_5],
# "regularseason"=>[:game_1, :game_2, :game_3]},
# 2013=>{"post_season" =>[:game_9, :game_10],
# "regularseason"=>[:game_6, :game_7, :game_8]},
# 2014=>{"post_season" =>[:game_11, :game_12, :game_13],
# "regularseason"=>[:game_14, :game_15]}}
This approach does not depend on "post_season" coincidentally preceding "regularseason" lexicographically. If, for example, it were decided to add the key "spring_training" and make that the first key to appear for each year in the returned hash, it would only be necessary to change the value of the second argument of reorder_key to "spring_training".
I have an array of IDs as listed below:
ids = [101153, 87218, 99589, 73109, 80660, 107784, 76392, 101501]
I have an array of hashes.
[
{"id"=>107786, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},
{"id"=>101501, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},
{"id"=>107786, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},
{"id"=>107784, "key"=>"ABC-2453", "hidden"=>true, "done"=>false},
{"id"=>107786, "key"=>"ABC-1345", "hidden"=>true, "done"=>false}
]
How do I filter an array of hashes where the ID is present from the array ids?
It isn't very hard. Iterate over hashes with select to see which ids are known. Use a Set for faster lookup.
require 'set'
ids = Set.new([101153, 87218, 99589, 73109, 80660, 107784, 76392, 101501])
hashes = [{"id"=>107786, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},{"id"=>101501, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},{"id"=>107786, "key"=>"ABC-2002", "hidden"=>true, "done"=>false},{"id"=>107784, "key"=>"ABC-2453", "hidden"=>true, "done"=>false},{"id"=>107786, "key"=>"ABC-1345", "hidden"=>true, "done"=>false}]
p hashes.select{ |h| ids.include?(h['id']) }
# [{"id"=>101501, "key"=>"ABC-2002", "hidden"=>true, "done"=>false}, {"id"=>107784, "key"=>"ABC-2453", "hidden"=>true, "done"=>false}]
How can I dynamically create a hash, giving each of its key a different value? For example:
hash = {}
(1..9).each{|key| hash[key] = ' '}
creates a hash with keys 1 to 9, where every key has the same value, a space. How can I do the same, keys 1 to 9, but with each key holding a different value.
If the values do not matter and just have to be different:
hash = Hash[(1..9).zip(1..9)]
# => {1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9}
hash = Hash[(1..9).zip('a'..'z')]
# => {1=>"a", 2=>"b", 3=>"c", 4=>"d", 5=>"e", 6=>"f", 7=>"g", 8=>"h", 9=>"i"}
For bigger hashes, performance can be improved by not creating intermediate arrays:
hash = (1..1000).inject({}) { | a, e | a[e] = e; a }
You can use this for dynamic hash creation with distinct key values.
hash = {}
(1..9).each_with_index{|key,index| hash[key] = index}
1..9
>> hash
hash
{1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>5, 7=>6, 8=>7, 9=>8}
p (1..9).zip(("a".."i").to_a.shuffle).to_h
# => {1=>"a", 2=>"d", 3=>"b", 4=>"h", 5=>"e", 6=>"g", 7=>"f", 8=>"i", 9=>"c"}
I have hash mapping
H = {
"alc" => "AL",
"alco" => "AL",
"alcoh" => "AL",
"alcohol" => "AL",
"alcoholic" => "AL",
}
now I want to use a regex to represent all the keys, like
H={
/^alc/ => "AL"
}
later on I want to use H["alc"] or H["alco"] to retrieve the value. But if I use regex, I can not get the value properly. What should I do?
class MyHash < Hash
def [](a)
self.select {|k| k =~ a}.shift[1]
end
end
result = MyHash.new
result[/^alc/] = "AL"
puts result['alcohol'] #=> 'AL'
I would create a subclass of the hash and then over write this method. This way you can still keep the regular hash functionality elsewhere.
Make a subclass, inherit Hash class and override [] behaviour so it checks whether it matches each regex in your hash and returns the corresponding value.