2

I have a db query which returns results like:

db_result.each {|row| puts row}
{"IP"=>"1.2.3.4","Field1"=>"abc","Field2"=>"123"}
{"IP"=>"1.2.3.4","Field1"=>"abc","Field2"=>"234"}
{"IP"=>"1.2.3.4","Field1"=>"bcd","Field2"=>"345"}
{"IP"=>"3.4.5.6","Field1"=>"bcd","Field2"=>"456"}
{"IP"=>"3.4.5.6","Field1"=>"bcd","Field2"=>"567"}

And want to put it into a hash like:

{
  "1.2.3.4" => {
    "abc" => ["123", "234"],
    "bcd" => "345"
  },
  "3.4.5.6" => {
    "bcd" => ["456", "567"]
  }
}

What I am currently doing is:

result_hash = Hash.new { |h, k| h[k] = {} }
db_result.each do |row|
  result_hash[row["IP"]] = Hash.new { |h, k| h[k] = [] } unless result_hash.has_key? row["IP"]
  result_hash[row["IP"]][row["Field1"]] <<  row["Field2"]
end 

Which works, however was wondering if there is a neater way.

3
  • 2
    There's nothing untidy about your method. You're doing a couple of operations, so it is more readable to leave it as you have it rather than try to cram it into a one-liner or obfuscate it down. Commented Aug 16, 2012 at 14:45
  • It depends on what you are using to handle the connection to the database. I think Sequel has the ability to map the results of a query into a hash of hashes based on a particular field. Commented Aug 16, 2012 at 15:01
  • Standard mysql2 client, so nothing fancy in there. Thanks for the saner formatting! Was just wondering if i was missing a trick. Commented Aug 16, 2012 at 15:06

2 Answers 2

1

Consider this a peer-review. As a recommendation for processing and maintenance...

I'd recommend the data structure you want be a little more consistent.

Instead of:

{
  "1.2.3.4" => {
    "abc" => ["123", "234"],
    "bcd" => "345"
  },
  "3.4.5.6" => {
    "bcd" => ["456", "567"]
  }
}

I'd recommend:

{
  "1.2.3.4" => {
    "abc" => ["123", "234"],
    "bcd" => ["345"]
  },
  "3.4.5.6" => {
    "abc" => [],
    "bcd" => ["456", "567"]
  }
}

Keep the same keys in each sub-hash, and make the values all be arrays. The code for processing that overall hash will be more straightforward and easy to follow.

Sign up to request clarification or add additional context in comments.

1 Comment

All the values should be arrays, that was my typo in copying out and obscuring the variable names. The number of sub-keys's is a decision making metric later on, so padding wont work. And I don't know what they'll be beforehand, so doing would be difficult anyhow.
1

I agree with Michael, there is nothing wrong with your method. The intent behind the code can be easily seen.

If you want to get fancy, here's one (of many) ways to do it:

x = [
  {"IP"=>"1.2.3.4","Field1"=>"abc","Field2"=>"123"},
  {"IP"=>"1.2.3.4","Field1"=>"abc","Field2"=>"234"},
  {"IP"=>"1.2.3.4","Field1"=>"bcd","Field2"=>"345"},
  {"IP"=>"3.4.5.6","Field1"=>"bcd","Field2"=>"456"},
  {"IP"=>"3.4.5.6","Field1"=>"bcd","Field2"=>"567"}
]


y = x.inject({}) do |result, row|
  new_row = result[row["IP"]] ||= {}
  (new_row[row["Field1"]] ||= []) << row["Field2"]

  result
end

I think this should yield the same time complexity as your method.

1 Comment

Okay, that's pretty cool. Was wondering how to use the inject stuff.

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.