views:

220

answers:

4

I have unsorted map of key value pairs.

input = {
  "xa" => "xavalue",
  "ab" => "abvalue",
  "aa" => "aavalue",
  "ba" => "bavalue",
}

Now I want to sort them by the key and cluster them into sections by the first character of the key. Similar to this:

output1 = {
  "a" => {
      "aa" => "aavalue",
      "ab" => "abvalue",  
  },
  "b" => {
    "ba" => "bavalue",
  },  
  "x" => {
    "xa" => "xavalue",
  },  
}
  1. While this is relatively trivial I am looking for a concise way to express this transformation from input to output1 in ruby. (My approach is probably too verbose as to ruby standards)

  2. You might also have noticed that maps are (usually) not ordered. So the above data structure will not work appropriately unless I manually sort the keys and wrap the access to the map. So how would I create a key ordered map in ruby? Or is there one already?

  3. If the ordered map approach is not that easy I would have to change the final structure into something like the following. Again I am looking for some concise ruby code to come from input to output2.

.

output2 = [
  {
    "name" => "a",
    "keys" => [ "aa", "ab" ],
    "values" => [ "aavalue", "abvalue" ],
  },
  {
    "name" => "b",
    "keys" => [ "ba" ],
    "values" => [ "bavalue" ],
  },
  {
    "name" => "x",
    "keys" => [ "xa" ],
    "values" => [ "xavalue" ],
  }
]
+1  A: 

I'm a pythonista but i've tried anyway:

class Hash
  def clustered
    clustered = Hash.new
    sort.each do | key, value |
      first = key[0,1]
      unless clustered.has_key?(first)
        clustered[first] = Hash.new        
      end
      clustered[first][key] = value
    end
    clustered
  end
end
Tim
`c[first] = Hash.new unless c.has_key? first`
J.F. Sebastian
Made it a community thingy.
Tim
Looks good but does not cover the ordering part.
tcurdt
+1  A: 
output = input.inject({}){ |h, p| k,v=p; (h[k[0..0]] ||= {})[k] = v; h}
Bkkbrad
Or even more concisely/cryptically: `input.inject({}){|h, p| k,v=p; l=k[0..0]; h.merge(l => (h[l] || {}).merge(k => v))}`
Thiago Arrais
The one from Bkkbrad is shorter :) ...but both miss the ordering part.
tcurdt
+2  A: 

As far as I know, there isn't the notion of a sorted hash/map in Ruby, so you're limited to good old arrays for this one. You may want to start from this code:

output = input.inject({}) { |acc, pair|
  letter = pair.first[0].chr
  acc[letter] ||= {}
  acc[letter][pair.first] = pair.last
  acc
}.sort

This will give you a data structure in the form

[ ["a", {"aa"=>"aavalue",
         "ab"=>"abvalue"}],
  ["b", {"ba"=>"bavalue"}],
  ["x", {"xa"=>"xavalue"}]]

By mapping the component arrays into hashes...

output.map {|pair| {pair.first => pair.last}}

You can transform it into something like

[{"a"=>{"aa"=>"aavalue",
        "ab"=>"abvalue"}},
 {"b"=>{"ba"=>"bavalue"}},
 {"x"=>{"xa"=>"xavalue"}}]

With a similar map, you can get to the last form you listed (output2):

output2 = output.map { |pair|
  hash = pair.last
  { 'name' => pair.first,
    'keys' => hash.keys,
    'values' => hash.values }
}
Thiago Arrais
Could you also show how you would do the transforms?
tcurdt
+1  A: 

Unfortunately none of the answers were complete. So I came up with this based on Thiago's solution:

output1 = input.inject({}) { |acc, pair|
  letter = pair.first[0].chr
  acc[letter] ||= {}
  acc[letter][pair.first] = pair.last
  acc
}.sort

output2 = output1.inject([]) { |acc, pair|
  acc << {
    'name' => pair.first,
    'keys' => pair.last.keys(),
    'values' => pair.last.values()
  }
  acc
}
tcurdt