How do I collect nested hash values into an array in ruby? - ruby

Say I have the following hash.
my_hash = {
'array1' => %w[
value1
value2
],
'array2' => %w[
value3
value4
]
}
How do I make an array that looks like
my_array = %w[value1 value2 value3 valuu4]

my_array = my_hash.values.flatten
=> ["value1", "value2", "value3", "value4"]

Flatten Hash Values
Use Hash#values to collect the values from your Hash, and then use Array#flatten to turn the result into a single Array rather than one containing nested arrays. For example:
my_hash.values.flatten
#=> ["value1", "value2", "value3", "value4"]

Related

Group by specific array value in hash and include key

I am trying to find out if it is possible to sort a Hash by a specific value if there are multiple values saved to a key.
Example Code:
{
key1: ["Value1", "Value2", "Value3"],
key2: ["Value1", "Value2", "Value3"],
key3: ["Value1", "Value2", "Value3"]
}
I would like to be able to sort by the values in Value2 or by Value1 or any specific value.
If key1 and key2 have the same Value2 they will be returned as:
{Value2: [key1, key2]}
Example:
Value2 representes pets:
For all the Value2 that have dog, the key will be saved under a new key, dog.
For all the Value2 that have cat, it will gather all keys that have Value2 as cat and group it.
{cat: ["key1", "key2"], dog: ["key3"]}
I am working in Ruby and VScode. I do not want to use an array or nested array because this is not efficient.
Given
{
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
Expected Output if asked to sort by the values in value2
{
dog: [key1, key3],
cat: [key2, key4]
}
This doesn't appear to be about sorting, but rather grouping.
It's a matter of iterating through each pair and building a new hash of the matching keys and values.
# A Hash where the default value is a new empty array.
# See https://stackoverflow.com/questions/30367487/creating-a-hash-with-values-as-arrays-and-default-value-as-empty-array
grouped = Hash.new { |h, k| h[k] = [] }
# Iterate through the Hash
h.each { |key, things|
# Get the 2nd thing
thing = things[1]
# Add its key to the thing's group
grouped[thing] << key
}
p grouped
Using #each_with_object to build up a hash as we iterate over the keys in the original hash.
data = {
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
data.keys.each_with_object(Hash.new([])) { |k, h|
h[data[k][1]] += [k]
}
# => {"dog"=>[:key1, :key3], "cat"=>[:key2, :key4]}
Maybe not the optimal solution but it returns the expected result. The code is at least groups by value and includes the key, resulting in:
{"cat"=>[:key2, :key4], "dog"=>[:key1, :key3]}
hash = {
key1: ["Cop", "dog", "house"],
key2: ["doctor", "cat", "apartment"],
key3: ["Chef", "dog", "house"],
key4: ["Cop", "cat", "apartment"]
}
def group_by(animal, hash)
keys = hash.select{|key, value| value[1] == animal }.keys
{animal => keys}
end
a = group_by("cat", hash)
b = group_by("dog", hash)
a.merge(b)
I am sure there is a more "direct" solution.

Insert in multiple arrays, in hash with key values in ruby

array1 = [[a,b,c],[1,2,3],[x,y,z]]
array2 = [[1,2,1],[2,2,2],[a,a,a]]
array3 = [[d,d,d], {a=>1,b=>2}]
#keys = key1,key2,key3
i need to to show the one single hash in below format
output = {"key1" => [[a,b,c],[1,2,3],[x,y,z]], "key2" => [[1,2,1],[2,2,2],[a,a,a]], "key3" => [[d,d,d], {a=>1,b=>2}] }
i am tried to in this code
Hash[#key.zip(array1)]
Multiple variables can't be manipulated as easily as elements in, say, an array. The easiest way to produce desired output in your case is to just write it out manually.
output = {
key1: array1,
key2: array2,
key3: array3,
}
If you want to be more dynamic, put your arrays into a usable data structure. Then you can use .zip or whatever.
keys = [:key1, :key2, :key3]
usable_arrays = [array1, array2, array3]
Hash[keys.zip(usable_arrays)]

Whats the ruby way to extract an array of values from an array of hashes?

Suppose I have an array like this:
starting_array = [{key1: 'someKey1Value', key2: 'someKey2Value'}, {key1: 'anotherKey1Value', key2: 'anotherKey2Value'}]
I want to end up with this:
desired_array = ['someKey2Value', 'anotherKey2Value']
Whats the best way to extract all the values for key2 into a separate array?
Use Array#map :-
starting_array.map { |hash| hash[:key2] }

Ruby pretty print strings

I have a map where key (a string) can have a very variable number of characters and I'd like to print it intelligently:
MAP = {
"key1" => "value1",
"key2" => "value2",
}
would print:
key1 -> value1
key2 -> value2
and
MAP = {
"key1" => "value1",
"key2" => "value2",
"key3_dam_it_you_are_a_big_one_indeed" => "value3",
}
would print:
key1 -> value1
key2 -> value2
key3_dam_it_you_are_a_big_one_indeed -> value3
he idea is that key1 and key2 would change their print-line according to the size of key3.
Thanks!
What about
class Hash
def nice_print
max_key_length = keys.map(&:length).max
each { |key, value| puts "#{key.ljust(max_key_length)} -> #{value}" }
end
end
and you call
MAP.nice_print

Building a hash in a conditional way

I am using Ruby on Rails 3.0.10 and I would like to build an hash key\value pairs in a conditional way. That is, I would like to add a key and its related value if a condition is matched:
hash = {
:key1 => value1,
:key2 => value2, # This key2\value2 pair should be added only 'if condition' is 'true'
:key3 => value3,
...
}
How can I do that and keep a "good" readability for the code? Am I "forced" to use the merge method?
I prefer tap, as I think it provides a cleaner solution than the ones described here by not requiring any hacky deleting of elements and by clearly defining the scope in which the hash is being built.
It also means you don't need to declare an unnecessary local variable, which I always hate.
In case you haven't come across it before, tap is very simple - it's a method on Object that accepts a block and always returns the object it was called on. So to build up a hash conditionally you could do this:
Hash.new.tap do |my_hash|
my_hash[:x] = 1 if condition_1
my_hash[:y] = 2 if condition_2
...
end
There are many interesting uses for tap, this is just one.
A functional approach with Hash.compact:
hash = {
:key1 => 1,
:key2 => (2 if condition),
:key3 => 3,
}.compact
Probably best to keep it simple if you're concerned about readability:
hash = {}
hash[:key1] = value1
hash[:key2] = value2 if condition?
hash[:key3] = value3
...
Keep it simple:
hash = {
key1: value1,
key3: value3,
}
hash[:key2] = value2 if condition
This way you also visually separate your special case, which might get unnoticed if it is buried within hash literal assignment.
I use merge and the ternary operator for that situation,
hash = {
:key1 => value1,
:key3 => value3,
...
}.merge(condition ? {:key2 => value2} : {})
Simple as this:
hash = {
:key1 => value1,
**(condition ? {key2: value2} : {})
}
Hope it helps!
IF you build hash from some kind of Enumerable data, you can use inject, for example:
raw_data.inject({}){ |a,e| a[e.name] = e.value if expr; a }
In case you want to add few keys under single condition, you can use merge:
hash = {
:key1 => value1,
:key2 => value2,
:key3 => value3
}
if condition
hash.merge!(
:key5 => value4,
:key5 => value5,
:key6 => value6
)
end
hash
First build your hash thusly:
hash = {
:key1 => value1,
:key2 => condition ? value2 : :delete_me,
:key3 => value3
}
Then do this after building your hash:
hash.delete_if {|_, v| v == :delete_me}
Unless your hash is frozen or otherwise immutable, this would effectively only keep values that are present.
Using fetch can be useful if you're populating a hash from optional attributes somewhere else. Look at this example:
def create_watchable_data(attrs = {})
return WatchableData.new({
id: attrs.fetch(:id, '/catalog/titles/breaking_bad_2_737'),
titles: attrs.fetch(:titles, ['737']),
url: attrs.fetch(:url, 'http://www.netflix.com/shows/breaking_bad/3423432'),
year: attrs.fetch(:year, '1993'),
watchable_type: attrs.fetch(:watchable_type, 'Show'),
season_title: attrs.fetch(:season_title, 'Season 2'),
show_title: attrs.fetch(:id, 'Breaking Bad')
})
end
Same idea as Chris Jester-Young, with a slight readability trick
def cond(x)
condition ? x : :delete_me
end
hash = {
:key1 => value1,
:key2 => cond(value2),
:key3 => value3
}
and then postprocess to remove the :delete_me entries

Resources