How do you define element uniqueness by multiple keys/attributes? - ruby

I have queried my database which gave me an array of hashes, where the keys in the hash are the column names. I want to keep only the hashes(array elements), that are unique according to multiple (3 columns). I have tried:
array.uniq { |item| item[:col1], item[:col2], item[:col3] }
as well as
array = array.inject([{}]) do |res, item|
if !res.any? { |h| h[:col1] == item[:col1] &&
h[:col2] == item[:col2] &&
h[:col3] == item[:col3] }
res << item
end
end
Does anyone have any ideas as to what's wrong or another way of going about this?
Thanks

It's unclear to me what you're asking for. My best guess is that given the array of single-association Hashes:
array = [{:col1 => 'aaa'}, {:col2 => 'bbb'}, {:col3 => 'aaa'}]
You'd like to have only one Hash per hash value; that is, remove the last Hash because both it and the first one have 'aaa' as their value. If so, then this:
array.uniq{|item| item.values.first}
# => [{:col1=>"aaa"}, {:col2=>"bbb"}]
Does what you want.
The other possibility I'm imagining is that given an array like this:
array2 = [{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'x'},
{:col1 => 'd', :col2 => 'b', :col3 => 'c', :col4 => 'y'},
{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'z'}]
You'd like to exclude the last Hash for having the same values for :col1, :col2, and :col3 as the first Hash. If so, then this:
array2.uniq{|item| [item[:col1], item[:col2], item[:col3]]}
# => [{:col1=>"a", :col2=>"b", :col3=>"c", :col4=>"x"},
# {:col1=>"d", :col2=>"b", :col3=>"c", :col4=>"y"}]
Does what you want.
If neither of those guesses are really want you're looking for, you'll need to clarify what you're asking for, preferably including some sample input and desired output.
I'll also point out that it's quite possible that you can accomplish what you want at the database query level, depending on many factors not presented.

If the no. of column is constant i.e. 3 you are better off creating a 3 level hash something like below
where whatever value you want to store is at 3rd level.
out_hash = Hash.new
array.each do |value|
if value[:col1].nil?
out_hash[value[:col1]] = Hash.new
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2].nil?
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2][:col3].nil?
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
end
end
I have not tested the code above its for giving you a idea...

Related

Replace keys with values in a Hash

What is the best way to replace all keys with values in a hash?
I came up with:
Hash[hash.map {|k,v| [v,k]}]
Is there a better solution?
There's a built-in method for that:
hash.invert
It's possible to invert a hash:
{ 'a' => 1, 'b' => 2 }.invert # => {1=>"a", 2=>"b"}
But be careful of the side-effects:
{ 'a' => 1, 'b' => 2, 'c' => 2 }.invert # => {1=>"a", 2=>"c"}
A hash's keys must be unique, but values don't have to be. When you invert the hash the duplicate values will collide, overwriting each other, with the last one winning.

I can't find a way to create a simple multidimensional array or hash in Ruby

You Ruby pros will laugh but I'm having such a hard time with this. I've searched and searched and tried a lot of different things but nothing seems right. I guess I'm just used to dealing with arrays in js and php. Here is what I want to do; consider this pseudo code:
i = 0
foreach (items as item) {
myarray[i]['title'] = item['title']
myarray[i]['desc'] = item['desc']
i++
}
Right, so then I can loop through myarray or access 'title' and 'desc' by the index (i). Simplest thing in the world. I've found a few ways to make it work in Ruby but they've all been really messy or confusing. I want to know the right way to do it, and the cleanest.
Unless you are actually updating my_array (which implies that there is probably a better way to do this), you probably want map instead:
items = [
{'title' => 't1', 'desc' => 'd1', 'other' => 'o1'},
{'title' => 't2', 'desc' => 'd2', 'other' => 'o2'},
{'title' => 't3', 'desc' => 'd3', 'other' => 'o3'},
]
my_array = items.map do |item|
{'title' => item['title'], 'desc' => item['desc'] }
end
items # => [{"title"=>"t1", "desc"=>"d1", "other"=>"o1"}, {"title"=>"t2", "desc"=>"d2", "other"=>"o2"}, {"title"=>"t3", "desc"=>"d3", "other"=>"o3"}]
my_array # => [{"title"=>"t1", "desc"=>"d1"}, {"title"=>"t2", "desc"=>"d2"}, {"title"=>"t3", "desc"=>"d3"}]
I'm not quite sure why you are trying to do this, as it seems like items is already an array with hashes inside it, and in my code below, myarray is exactly the same as items.
Try using each_with_index instead of a foreach loop:
items.each_with_index do |item, index|
myarray[index] = item
end
If you have extra attributes in each item, such as a id or something, then you would want to remove those extra attributes before you add the item to myarray.
titles = ["t1", "t2", "t3"]
descs = ["d1", "d2", "d3"]
h= Hash.new
titles.each.with_index{ |v,i| h[i] = {title: "#{v}" } }
puts h[0][:title] #=> t1
puts h #=> {0=>{:title=>"t1"}, 1=>{:title=>"t2"}...}
descs.each.with_index{ |v,i| h[i] = h[i].merge( {desc: "#{v}" } ) }
puts h[0][:desc] #=> d1
puts h #=> {0=>{:title=>"t1", :desc=>"d1"}, 1=>...

Getting an array of hash values given specific keys

Given certain keys, I want to get an array of values from a hash (in the order I gave the keys). I had done this:
class Hash
def values_for_keys(*keys_requested)
result = []
keys_requested.each do |key|
result << self[key]
end
return result
end
end
I modified the Hash class because I do plan to use it almost everywhere in my code.
But I don't really like the idea of modifying a core class. Is there a builtin solution instead? (couldn't find any, so I had to write this).
You should be able to use values_at:
values_at(key, ...) → array
Return an array containing the values associated with the given keys. Also see Hash.select.
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
h.values_at("cow", "cat") #=> ["bovine", "feline"]
The documentation doesn't specifically say anything about the order of the returned array but:
The example implies that the array will match the key order.
The standard implementation does things in the right order.
There's no other sensible way for the method to behave.
For example:
>> h = { :a => 'a', :b => 'b', :c => 'c' }
=> {:a=>"a", :b=>"b", :c=>"c"}
>> h.values_at(:c, :a)
=> ["c", "a"]
i will suggest you do this:
your_hash.select{|key,value| given_keys.include?(key)}.values

Convert a hash into another hash in Ruby

I have a hash of strings
navigable_objects = { 'Dashboard' => root_path,
'Timesheets' => timesheets_path,
'Clients' => clients_path,
'Projects' => projects_path,
}
I want to convert them into another hash where the key is again the key, but the value is either the string 'active' or empty string depending on whether the current controller name contains the key.
For example, lets say that the current controller name is "ClientsController". The result I should get is:
{ 'Dashboard' => '',
'Timesheets' => '',
'Clients' => 'active',
'Projects' => ''
}
Here is how I am currently doing it:
active = {}
navigable_objects.each do |name, path|
active[name] = (controller.controller_name.include?(name)) ? 'active' : '')
end
I feel that while this works, there is a better way to do this in Ruby, possibly using inject or each_with_objects?
UPDATE: I posted another answer that I think is better for your situation. I'm leaving this one un-edited though because it has merit on its own for similar problems.
Here's the way I'd do it:
Hash[*navigable_objects.map{ |k,v| [k, controller.controller_name.include?(k) ? 'active' : ''] }.flatten]
You can run map on a hash that gets key and value pairs as input to the block. Then you can construct pairs of key/values into arrays as the output. Finally, Running Hash[*key_value_pairs.flatten] is a nice trick to turn it back into a hash. This works because you can pass an array of arguments to the Hash[] constructor to generate a hash (Hash[1, 2, 3, 4] => { 1 => 2, 3 => 4 }). And flatten turns the key value pairs into an array, and * operator turns an array into a list of arguments.
Here's a verbose version in case you want to see more clearly what's going on:
key_value_pairs = navigable_objects.map do |key, value|
new_value = controller.controller_name.include?(k) ? 'active' : ''
[key, new_value]
end
new_hash = Hash[*key_value_pairs.flatten]
Update: This above is compatible with ruby 1.8. As Andrew pointed out in the comments:
In 1.9 you don't need the * or flatten as Hash[] takes key-value array
pairs
NOTE: I already answered but I'm posting this as a separate answer because it's better for your specific situation, but my other answer still has merit on its own.
Since you're not using the values of the hash at all, you can use each_with_object:
navigable_objects.keys.each_with_object({}) { |k,h| h[k] = controller.controller_name.include?(k) ? 'active' : '' }
Or more verbosely:
new_hash = navigable_objects.keys.each_with_object({}) do |key, hash|
hash[key] = controller.controller_name.include?(key) ? 'active' : ''
end
If your result was based on the values too, then my other solution would work whereas this one wouldn't.
Since Ruby 2.0, there is to_h:
navigable_objects.map do |name, path|
[name, controller.controller_name.include?(name)) ? 'active' : '']
end.to_h
I don't think it's very efficient, but for small Hashes it's an elegant way.
There are many many ways to accomplish this. This is just the way I'd do it.
With inject
active = navigable_objects.inject({}) do |h, obj|
h[obj[0]] = controller.controller_name.include?(obj[0]) ? 'active' : ''
h
end
When you call inject on a hash, the block is passed the thing you are injecting into (in this case a hash) and an array with the first element being the key and the last element being the value.

Is saving a hash in another hash common practice?

I'd like to save some hash objects to a collection (in the Java world think of it as a List). I search online to see if there is a similar data structure in Ruby and have found none. For the moment being I've been trying to save hash a[] into hash b[], but have been having issues trying to get data out of hash b[].
Are there any built-in collection data structures on Ruby? If not, is saving a hash in another hash common practice?
If it's accessing the hash in the hash that is the problem then try:
>> p = {:name => "Jonas", :pos => {:x=>100.23, :y=>40.04}}
=> {:pos=>{:y=>40.04, :x=>100.23}, :name=>"Jonas"}
>> p[:pos][:x]
=> 100.23
There shouldn't be any problem with that.
a = {:color => 'red', :thickness => 'not very'}
b = {:data => a, :reason => 'NA'}
Perhaps you could explain what problems you're encountering.
The question is not completely clear, but I think you want to have a list (array) of hashes, right?
In that case, you can just put them in one array, which is like a list in Java:
a = {:a => 1, :b => 2}
b = {:c => 3, :d => 4}
list = [a, b]
You can retrieve those hashes like list[0] and list[1]
Lists in Ruby are arrays. You can use Hash.to_a.
If you are trying to combine hash a with hash b, you can use Hash.merge
EDIT: If you are trying to insert hash a into hash b, you can do
b["Hash a"] = a;
All the answers here so far are about Hash in Hash, not Hash plus Hash, so for reasons of completeness, I'll chime in with this:
# Define two independent Hash objects
hash_a = { :a => 'apple', :b => 'bear', :c => 'camel' }
hash_b = { :c => 'car', :d => 'dolphin' }
# Combine two hashes with the Hash#merge method
hash_c = hash_a.merge(hash_b)
# The combined hash has all the keys from both sets
puts hash_c[:a] # => 'apple'
puts hash_c[:c] # => 'car', not 'camel' since B overwrites A
Note that when you merge B into A, any keys that A had that are in B are overwritten.

Resources