1

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.

2
  • 1
    It may go against Perl-ish tradition, but you probably want to prefer clarity to brevity - just because you can make it a one-liner, doesn't mean you should. What you do want is to be able to come back to it in 6 months, and not think "What the hell was whoever-wrote-this thinking?" Commented Mar 6, 2013 at 22:46
  • 1
    In Ruby, you can have clarity and brevity, with properly factored code. Commented Mar 6, 2013 at 23:07

4 Answers 4

4

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"]}
Sign up to request clarification or add additional context in comments.

Comments

2

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.

5 Comments

Ah, this is very interesting and I am definitely thinking Perl vs Ruby so this seems to be a good nudge in the right direction...
@Jim I came from a Perl background myself. In Perl, data structures are easier than OO. In Ruby, it's the opposite.
Even modeling the elements as objects, your answer doesn't get to the OP's desired result which was a mapping from :name => [set of :data]. items.group_by(&:name).each_with_object({}) {|(k,v),m| m[k] = v.map(&:data) } get's all the way.
My point was that you don't necessarily have to think in data structures, either as input or as output. I wasn't giving him his desired result, because I was asking him to rethink his desired result. :)
If I had code like most of these answers, I'd refactor. Too much complexity for the type of work being done.
0

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.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.