I've an array contains hashes, I want to filter few parameters from the hash and insert the filtered data in another array but am not succeed below is the sample data I've used
a = Array.new
a = [
{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"},
{"name"=>"name2", "age"=>"26", "sex"=> "M", "city"=>"Banglore"}
]
line_item = Array.new
hash_data = {}
a.each do |datas|
hash_data[:name] = datas["name"]
hash_data[:age] = datas["age"]
line_item << hash_data
end
I am getting this result:
[
{:name=>"name2", :age=>"26"},
{:name=>"name2", :age=>"26"}
]
But am expecting this:
[
{:name=>"hello", :age=>"12"},
{:name=>"name2", :age=>"26"}
]
Somebody please help to sort out this, Thanks in advance
Defining the hash outside the loop means that you keep adding the same hash object again (while overwriting its previous values). Instead, create a fresh hash within the loop:
line_items = []
a.each do |datas|
hash_data = {}
hash_data[:name] = datas["name"]
hash_data[:age] = datas["age"]
line_items << hash_data
end
The code looks a bit unidiomatic. Let's refactor it.
We can set the keys right within the hash literal:
line_items = []
a.each do |datas|
hash_data = { name: datas["name"], age: datas["age"] }
line_items << hash_data
end
We can get rid of the hash_data variable:
line_items = []
a.each do |datas|
line_items << { name: datas["name"], age: datas["age"] }
end
And we can use map to directly transform the array:
line_items = a.map { |h| { name: h["name"], age: h["age"] } }
#=> [{:name=>"hello", :age=>"12"}, {:name=>"name2", :age=>"26"}]
You can get the expected result with a combination of map and slice
a = [
{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"},
{"name"=>"name2", "age"=>"26", "sex"=> "M", "city"=>"Banglore"}
]
a.map{ |e| e.slice("name", "age") }
#=> [{"name"=>"hello", "age"=>"12"}, {"name"=>"name2", "age"=>"26"}]
map: Returns Array containing the values returned by block
slice: Returns Hash including only the specified keys
In your loop you are essentially populating line_item with hash_data twice. This is the same object however. You can remedy this by using .dup.
a.each do |datas|
hash_data[:name]=datas["name"]
hash_data[:age]=datas["age"]
line_item << hash_data.dup # <- here
end
irb(main):044:0> line_item
=> [{:name=>"hello", :age=>"12"}, {:name=>"name2", :age=>"26"}]
Edit: I prefer rado's suggestion of moving your definition of hash_data inside the loop over using .dup. It solves the problem more than treating the symptom.
I think a lot of people are over complicating this.
You can achieve this using the following:
a.map { |hash| hash.select { |key, _value| key == 'name' || key == 'age' } }
If you want to return an array, you should nearly always be using map, and select simply selects the key - value pairs that match the criteria.
If you're set on having symbols as the keys, you can call symbolize_keys on the result.
I'll expand the code so it's a little more readable, but the one liner above works perfectly:
a.map do |hash|
hash.select do |key, _value|
key == 'name' || key == 'age'
end
end
On the first line hash_data[:name]=datas["name"] you are setting the key of the hash. That's why when the loop iterate again, it is overriding the value and after that push the new result to the hash.
One solution with reusing this code is just to put the hash_data = {} on the first line of your loop. This way you will have a brand new hash to work with on every iteration.
Also I would recommend you to read the docs about the Hash module. You will find more useful methods there.
If you want for all keys you can do this
array = [{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"}, {"name"=>"name2", "age"=>"26""sex"=> "M", "city"=>"Banglore"}]
new_array = array.map{|b| b.inject({}){|array_obj,(k,v)| array_obj[k.to_sym] = v; array_obj}}
Ref: inject
Happy Coding
Related
Given an array of hashes, I want to create a method that returns a hash where the keys are the unique values of the hashes in the array.
For example, I'd like to take
[
{foo: 'bar', baz: 'bang'},
{foo: 'rab', baz: 'bang'},
{foo: 'bizz', baz: 'buzz'}
]
and return
{
foo: ['bar', 'rab', 'bizz'],
baz: ['bang', 'buzz']
}
I am currently accomplishing this using:
def my_fantastic_method(data)
response_data = { foo: [], baz: []}
data.each { |data|
data.attributes.each { |key, value|
response_data[key.to_sym] << value
}
}
response_data.each { |key, value| response_data[key] = response_data[key].uniq }
response_data
end
Is there a more elegant way of doing this? Thanks!
Your current approach is already pretty good; I don't see much room for improvement. I would write it like this:
def my_fantastic_method(data_list)
data_list.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |data, result|
data.attributes.each do |key, value|
result[key.to_sym] << value
end
end
end
By setting a default value on each hash value, I have eliminated the need to explicitly declare foo: [], bar: [].
By using each_with_object, I have eliminated the need to declare a local variable and explicitly return it at the end.
By using Set, there is no need to call uniq on the final result. This requires less code, and is more performant. However, if you really want the final result to be a mapping to Arrays rather than Sets, then you would need to call to_a on each value at the end of the method.
I have used different variable names for data_list and data. Call these whatever you like, but it's typically considered bad practice to shadow outer variables.
Here are a couple of one-liners. (I'm pretty sure #eiko was being facetious, but I'm proving him correct)
This one reads well and is easy to follow (caveat: requires Ruby 2.4+ for transform_values):
array.flat_map(&:entries).group_by(&:first).transform_values{|v| v.map(&:last).uniq}
Here's another, using the block form of merge to specify an alternate merge method, which in this case is combining the values into a uniq array:
array.reduce{|h, el| h.merge(el){|k, old, new| ([old]+[new]).flatten.uniq}}
You already have a pretty good answer, but I felt golfy and so here is a shorter one:
def the_combiner(a)
hash = {}
a.map(&:to_a).flatten(1).each do |k,v|
hash[k] ||= []
hash[k].push(v)
end
hash
end
Try this:
array.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| {k => v.map(&:last)} }
OR
a.inject({}) {|old_h, new_h|
new_h.each_pair {|k, v|
old_h.key?(k) ? old_h[k] << v : old_h[k]=[v]};
old_h}
If, as in the example, all hashes have the same keys, you could do as follows.
arr = [{ foo: 'bar', baz: 'bang' },
{ foo: 'rab', baz: 'bang' },
{ foo: 'bizz', baz: 'buzz' }]
keys = arr.first.keys
keys.zip(arr.map { |h| h.values_at(*keys) }.transpose.map(&:uniq)).to_h
#=> {:foo=>["bar", "rab", "bizz"], :baz=>["bang", "buzz"]}
The steps are as follows.
keys = arr.first.keys
#=> [:foo, :baz]
a = arr.map { |h| h.values_at(*keys) }
#=> [["bar", "bang"], ["rab", "bang"], ["bizz", "buzz"]]
b = a.transpose
#=> [["bar", "rab", "bizz"], ["bang", "bang", "buzz"]]
c = b.map(&:uniq)
#=> [["bar", "rab", "bizz"], ["bang", "buzz"]]
d = c.to_h
#=> <array of hashes shown above>
I've created the following hash keys with values parsed from PDF into array:
columns = ["Sen", "P-Hire#", "Emp#", "DOH", "Last", "First"]
h = Hash[columns.map.with_index.to_h]
=> {"Sen"=>0, "P-Hire#"=>1, "Emp#"=>2, "DOH"=>3, "Last"=>4, "First"=>5}
Now I want to update the value of each key with 6 equivalent values from another parsed data array:
rows = list.text.scan(/^.+/)
row = rows[0].tr(',', '')
#data = row.split
=> ["2", "6", "239", "05/05/67", "Harp", "Erin"]
I can iterate over #data in the view and it will list each of the 6 values. When I try to do the same in the controller it sets the same value to each key:
data.each do |e|
h.update(h){|key,v1| (e) }
end
=>
{"Sen"=>"Harper", "P-Hire#"=>"Harper", "Emp#"=>"Harper", "DOH"=>"Harper", "Last"=>"Harper", "First"=>"Harper"
So it's setting the value of each key to the last value of the looped array...
I would just do:
h.keys.zip(#data).to_h
If the only purpose of h is as an interim step getting to the result, you can dispense with it and do:
columns.zip(#data).to_h
There are several ways to solve this problem but a more direct and straight forward way would be:
columns = ["Sen", "P-Hire#", "Emp#", "DOH", "Last", "First"]
...
#data = row.split
h = Hash.new
columns.each_with_index do |column, index|
h[column] = #data[index]
end
Another way:
h.each do |key, index|
h[key] = #data[index]
end
Like I said, there are several ways of solving the issue and the best is always going to depend on what you're trying to achieve.
If I have an array of hashes, each with a day key:
[
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
What is the best way to convert it to a hash with sorted day values as the keys:
{
:1=>[{:day=>1,:name=>'Ben'}],
:4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
I'm using Ruby 1.9.2 and Rails 3.1.1
Personally, I wouldn't bother "sorting" the keys (which amounts to ordering-by-entry-time in Ruby 1.9) until I actually needed to. Then you can use group_by:
arr = [{:day=>4,:name=>'Jay'}, {:day=>1,:name=>'Ben'}, {:day=>4,:name=>'Jill'}]
arr.group_by { |a| a[:day] }
=> {4=>[{:day=>4, :name=>"Jay"}, {:day=>4, :name=>"Jill"}],
1=>[{:day=>1, :name=>"Ben"}]}
Instead, sort the keys when you actually need them.
Assuming you array is called is list, here's one way using the reduce method:
list.reduce({}) { |hash, item|
(hash[item[:day]] ||= []) << item; hash
}
Here's another using the map method, but you have to carry a holder variable around:
hash = {}
list.each { |item|
(hash[item[:day]] ||= []) << item
}
Once you have the unsorted hash say in variable foo, you can sort it as,
Hash[foo.sort]
Simple answer:
data = [
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
#expected solution
sol = {
1=>[{:day=>1,:name=>'Ben'}],
4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
res = {}
data.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
Problem with this solution: The hash is not sorted as requested. (Same problem has Anurags solution).
So you must modify the answer a bit:
res = {}
data.sort_by{|h| h[:day]}.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
In Rails you can use OrderedHash:
ActiveSupport::OrderedHash[arr.group_by { |a| a[:day] }.sort_by(&:first)]
Update: In fact in Ruby 1.9 hash is ordered, so using ActiveSupport extension is not required:
Hash[arr.group_by { |a| a[:day] }.sort_by(&:first)]
Lets say I have an Array of content_categories (content_categories = user.content_categories)
I now want to add every element belonging to a certain categorie to content_categories with the category as a key and the the content-item IDs as elements of a set
In PHP something like this is possible:
foreach ($content_categories as $key => $category) {
$contentsByCategoryIDArray = Category.getContents($category[id])
$content_categories[$key][$contentsByCategoryIDArray]
}
Is there an easy way in rails to do this?
Greets,
Nico
Your question isn't really a Rails question, it's a general Ruby programming question.
Your description isn't very clear, but from what I understand, you want to group IDs for common categories using a Hash. There are various other ways of doing this, but this is easy to understand::
ary = [
'cat1', {:id => 1},
'cat2', {:id => 2},
'cat1', {:id => 3}
]
hsh = {}
ary.each_slice(2) { |a|
key,category = a
hsh[key] ? hsh[key] << category[:id] : hsh[key] = [category[:id]]
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
I'm using a simple Array with a category, followed by a simple hash representing some object instance, because it makes it easy to visualize. If you have a more complex object, replace the hash entries with those objects, and tweak how you access the ID in the ternary (?:) line.
Using Enumerable.inject():
hsh = ary.each_slice(2).inject({}) { |h,a|
key,category = a
h[key] ? h[key] << category[:id] : h[key] = [category[:id]]
h
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
Enumerable.group_by() could probably shrink it even more, but my brain is fading.
I'd use Enumerable#inject
content_categories = content_categories_array.inject({}){ |memo, category| memo[category] = Category.get_contents(category); memo }
Hash[content_categories.map{|cat|
[cat, Category.get_contents(cat)]
}]
Not really the right answer, because you want IDs in your array, but I post it anyway, because it's nice and short, and you might actually get away with it:
content_categories.group_by(&:category)
content_categories.each do |k,v|
content_categories[k] = Category.getContents(v)
end
I suppose it's works
If i understand correctly, content_categories is an array of categories, which needs to be turned into a hash of categories, and their elements.
content_categories_array = content_categories
content_categories_hash = {}
content_categories_array.each do |category|
content_categories_hash[category] = Category.get_contents(category)
end
content_categories = content_categories_hash
That is the long version, which you can also write like
content_categories = {}.tap do |hash|
content_categories.each { |category| hash[category] = Category.get_contents(category) }
end
For this solution, content_categories must be a hash, not an array as you describe. Otherwise not sure where you're getting the key.
contents_by_categories = Hash[*content_categories.map{|k, v| [k, Category.getContents(v.id)]}]
I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today