Question

I'm a Perl developer trying to learn Ruby... So, I'll demonstrate in Perl what I'm trying to accomplish in Ruby and then summarize at the end...

#!/usr/bin/perl -w

use strict;
use Data::Dumper;

# Given the following data structure (an array of hashes)
my $j  = [ 
            {   
                id   => 1,
                key1 => 'name1',
                key2 => 'data',
                key3 => 'data',
                key4 => 'foo',
            },  
            {   
                id   => 2,
                key1 => 'name1',
                key2 => 'data',
                key3 => 'data',
                key4 => 'bar',
            },  
            {   
                id   => 3,
                key1 => 'name2',
                key2 => 'data',
                key3 => 'data',
                key4 => 'baz',
            },  
        ];  

print ~~@$j,"\n";
print Dumper($j)."\n";

my $myHash; # make it a reference to a hoa.

for my $array ( @{$j} )
{
   # the key to my new key-name is always known
    push(@{$myHash->{$array->{key1}}},$array->{key4});
}

print Dumper($myHash)."\n";

And the output:

Initial array:

$VAR1 = [
          {
            'key2' => 'data',
            'key4' => 'foo',
            'key1' => 'name1',
            'id' => 1,
            'key3' => 'data'
          },
          {
            'key2' => 'data',
            'key4' => 'bar',
            'key1' => 'name1',
            'id' => 2,
            'key3' => 'data'
          },
          {
            'key2' => 'data',
            'key4' => 'baz',
            'key1' => 'name2',
            'id' => 3,
            'key3' => 'data'
          }
        ];

What I'm trying to get:

$VAR1 = {
          'name2' => [
                       'baz'
                     ],
          'name1' => [
                       'foo',
                       'bar'
                     ]
        };

...and I'm trying to do it in as succinct code as possible, which has proven to be a pain given my lack of Ruby goodness. I've tried multiple attempts at this including several tries at map similar to the same code structure I used in Perl to no avail.

That said, I did find the following snippet, just now, which almost works but I'm sure I'm doing something wrong...

h = Hash[j.collect {|array| [array.key1,array.key4] }]

This gets me the right hash key but it doesn't push the key4 value into an array of the hash. Still looking but some nudge in the right direction would be appreciated. If I find the answer before getting an answer here, I'll answer the question for the edification of future readers.

EDIT! I need to clarify something that I just figured out and is probably the wrench in my clock. The data that I'm getting is not a pure array. It's actually an object from DataMapper.

Was it helpful?

Solution

You're performing a group by operation. Enumerable#group_by produces a hash whose keys are the values from the block and whose values are arrays of all initial elements that has that key.

You can then use Enumerable#each_with_object to create a new hash with those keys, but the values reduced to just the key4 element you desired.

a = [
  { id: 1, key1: 'name1', key2: 'data', key3: 'data', key4: 'foo', },
  { id: 2, key1: 'name1', key2: 'data', key3: 'data', key4: 'bar', },
  { id: 3, key1: 'name2', key2: 'data', key3: 'data', key4: 'baz',  },
]

res = a.group_by { |e| e[:key1] }.
        each_with_object({}) { |(k,v),m| m[k] = v.map{|e| e[:key4]}  }

puts res.inspect
# => {"name1"=>["foo", "bar"], "name2"=>["baz"]}

OTHER TIPS

When you started with "given the following data structure (array of hashes)" you were already thinking in Perl. In idiomatic Ruby, it would be an array of objects. In fact, this is typical of the output of a database call, etc. A sample class:

class Item
  attr_accessor :id, :name, :data, :foo
end

If you had an array of such items, your answer might look like this:

items.group_by(&:name)

which is of course much cleaner and more expressive.

Note that the above doesn't give you the exact data structure you are asking for. Slinging data structures around is an anti-pattern. The output you're asking for is certainly an intermediate structure that will be used for something else entirely (printing output in a view; serializing for storage, etc). Therefore, I would argue that it isn't necessary. You can do what you need with a grouped list of objects.

Here is a oneliner. Assume 'ar' is bound to your array. Assign r={} and then:

ar.map { |a| { a[:key1] => a[:key4] }}.each { |m| m.each { |key,val| r[key] = if not r[key] then [val] else r[key].push(val) end }

The result is in r.

Another way:

Hash[j.chunk {|e| e['key1'] }.map {|k, ary| [k, ary.map {|x| x['key4'] }] }]
=> {"name1"=>["foo", "bar"], "name2"=>["baz"]}

but I think I like dbenhur's answer the best so far.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top