2
\$\begingroup\$

Just for the sake of understanding. I know JSON.parse could do this pretty easily, but without using that, how could this be made cleaner or more efficient?

string = "ounces: 10; contains_toy: false"
result = string.split("; ")
result = result.map{|x| x = x.split(": "); Hash[x.first.to_sym, x.last] }
result = result.reduce(:merge)

Desired result:

{:ounces=>"10", :contains_toy=>"false"}
\$\endgroup\$
  • 1
    \$\begingroup\$ Also for the sake of understanding: How would JSON.parse help? The string isn't close to being JSON. Keys would have to be quoted, commas instead of semicolons, braces around everything. \$\endgroup\$ – Flambino Nov 29 '15 at 23:08
  • \$\begingroup\$ I was thinking something like this : result = JSON.parse "{#{string.gsub(/([a-z_]+):/, '"\1":')}}" was a little cleaner \$\endgroup\$ – newUserNameHere Nov 29 '15 at 23:19
  • 1
    \$\begingroup\$ With gsub + parse you'd probably hit the same issue that any sort of naïve string manipulation (including the code in my answer) has: Delimiter-tokens being taken taken out of context, and breaking things. \$\endgroup\$ – Flambino Nov 29 '15 at 23:28
6
\$\begingroup\$
  1. Chain your calls. Don't update the same variable again and again with very different values.

  2. Just construct a hash straight away with each_with_object or construct an array of pairs, and turn that into a hash.

  3. Any sort of splitting/scanning is prone to errors if the string contains tokens that aren't part of the syntax. I.e. a nested string with a semicolon in it will be split as though it's a delimiter.

Anyway, you can do this, for instance:

result = Hash[ string.scan(/(\w+):\s+([^;]+)/) ]

though that'll give you string keys.

To avoid that, you could do:

pairs = string.scan(/(\w+):\s+([^;]+)/).map { |k,v| [k.to_sym, v.strip] }
result = Hash[pairs]

or

result = string.scan(/(\w+):\s+([^;]+)/).each_with_object({}) do |(k,v), hash|
  hash[k.to_sym] = v.strip
end

Another, much less robust way would be:

pairs = string.split(/[:;]/)
  .map(&:strip)
  .each_slice(2)
  .map { |k,v| [k.strip.to_sym, v.strip] }
result = Hash[pairs]

Edit: And the totally unsafe option would be

result = eval("{#{string.tr(';', ',')}}")

On the plus side, your values have proper types (not all strings). On the minus side, eval is evil, and should always be avoided. So, really, don't do this.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for the detailed explanation as well as the variations! Great explanation :) \$\endgroup\$ – newUserNameHere Nov 29 '15 at 23:23
  • 1
    \$\begingroup\$ @newUserNameHere Thanks - added another (really poor) alternative. But it's the shortest one yet. \$\endgroup\$ – Flambino Nov 29 '15 at 23:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.