简体   繁体   中英

Convert this string to array of hashes

In Ruby or Rails What's the cleanest way to turn this string:

"[{one:1, two:2, three:3, four:4},{five:5, six:6}]"

into an array of hashes like this:

[{one:1, two:2, three:3, four:4},{five:5, six:6}]

Here is a one-liner on two lines:

s.split(/}\s*,\s*{/).
  map{|s| Hash[s.scan(/(\w+):(\d+)/).map{|t| proc{|k,v| [k.to_sym, v.to_i]}.call(*t)}]}

NB I was using split(":") to separate keys from values, but @Cary Swoveland's use of parens in the regex is cooler. He forgot the key and value conversions, however.

Or a bit shorter, but uses array indexing instead of the proc , which some may find unappealing:

s.split(/}\s*,\s*{/).
  map{|s| Hash[s.scan(/(\w+):(\d+)/).map{|t| [t[0].to_sym, t[1].to_i]}]}

Result:

=> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]

Explanation: Start from the end. The last map processes a list of strings of the form "key: value" and returns a list of [:key, value] pairs. The scan processes one string of comma-separated key-value pairs into a list of "key: value" strings. Finally, the initial split separates the brace-enclosed comma-separated strings.

Try this:

"[{one:1, two:2, three:3, four:4},{five:5, six:6}]".
  split(/\}[ ]*,[ ]*\{/).
  map do |h_str| 
    Hash[
      h_str.split(",").map do |kv| 
        kv.strip.
          gsub(/[\[\]\{\}]/, '').
          split(":")
      end.map do |k, v|
        [k.to_sym, v.to_i]
      end
    ]
  end

Not pretty, not optimized, but solves it. (It was fun to do, though :) )

a = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
array = []
a.gsub(/\[|\]/, '').split(/{|}/).map{ |h| h if h.length > 0 && h != ','}.compact.each do |v|
  hsh = {}
  v.split(',').each do |kv|
    arr = kv.split(':')
    hsh.merge!({arr.first.split.join.to_sym => arr.last.to_i})
  end
  array << hsh
end

If you want me to explain it, just ask.

You could do as below.

Edit: I originally prepared this answer in haste, while on the road, on a borrowed computer with an unfamiliar operating system (Windows). After @sawa pointed out mistakes, I set about fixing it, but became so frustrated with the mechanics of doing so that I gave up and deleted my answer. Now that I'm home again, I have made what I believe are the necessary corrections.

Code

def extract_hashes(str)
  str.scan(/\[?{(.+?)\}\]?/)
     .map { |arr| Hash[arr.first
                          .scan(/\s*([a-z]+)\s*:\d*(\d+)/)
                          .map { |f,s| [f.to_sym, s.to_i] }
                      ]
          }
end

Example

str = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
extract_hashes(str)
  #=> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]

Explanation

For str in the example above,

a = str.scan(/\[?{(.+?)\}\]?/)
  #=> [["one:1, two:2, three:3, four:4"], ["five:5, six:6"]]

Enumerable#map first passes the first element of a into the block and assigns it to the block variable:

arr #=> ["one:1, two:2, three:3, four:4"]

Then

b = arr.first
  #=> "one:1, two:2, three:3, four:4"
c = b.scan(/\s*([a-z]+)\s*:\d*(\d+)/)
  #=> [["one", "1"], ["two", "2"], ["three", "3"], ["four", "4"]]
d = c.map { |f,s| [f.to_sym, s.to_i] }
  #=> [[:one, 1], [:two, 2], [:three, 3], [:four, 4]]
e = Hash[d]
  #=> {:one=>1, :two=>2, :three=>3, :four=>4}

In Ruby 2.0, Hash[d] can be replaced with d.to_h .

Thus, the first element of a is mapped to e .

Next, the outer map passes the second and last element of a into the block

arr #=> ["five:5, six:6"]

and we obtain:

Hash[arr.first
        .scan(/\s*([a-z]+)\s*:\d*(\d+)/)
        .map { |f,s| [f.to_sym, s.to_i] }
    ]
  #=> {:five=>5, :six=>6}

which replaces a.last .

Another approach: Your string looks like a YAML or JSON -definition:

YAML

A slightly modified string works:

require 'yaml'
p YAML.load("[ { one: 1, two: 2, three: 3, four: 4}, { five: 5, six: 6 } ]")
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]

There are two problems:

  1. The keys are strings, no symbols
  2. You need some more spaces ( one:1 is not recognized, you need a one: 1 ).

For problem 1 you need a gsub(/:/, ': ') (I hope there are no other : in your string)

For problem 2 was already a question : Hash[a.map{|(k,v)| [k.to_sym,v]}] Hash[a.map{|(k,v)| [k.to_sym,v]}]

Full example:

require 'yaml'
input = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
input.gsub!(/:/, ': ')  #force correct YAML-syntax
p YAML.load(input).map{|a| Hash[a.map{|(k,v)| [k.to_sym,v]}]}
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]

JSON

With json you need additonal " , but the symbolization is easier:

require 'json'
input = '[ { "one":1, "two": 2, "three": 3, "four": 4},{ "five": 5, "six": 6} ]'   

p JSON.parse(input)
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]
p JSON.parse(input, :symbolize_names => true)
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]

Example with original string:

require 'json'

input = "[{one: 1, two: 2, three:3, four:4},{five:5, six:6}]"
input.gsub!(/([[:alpha:]]+):/, '"\1":')    
p JSON.parse(input)
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]
p JSON.parse(input, :symbolize_names => true)
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM