将字符串值转换为哈希表

3

我有一个字符串需要转换成哈希表。字符串的键始终是一个符号,而值始终是一个整数:

"a=1, b=2, c=3, d=4"

这个字符串应该返回一个看起来像这样的哈希值:
{ :a => 1, :b => 2, :c => 3, :d => 4 }

我尝试了几种不同的方法,但目前为止最接近的是将字符串分两次,首先按逗号和空格分割,然后按等号分割,并创建符号:

def str_to_hash(str)
  array = str.split(', ').map{|str| str.split('=').map{|k, v| [k.to_sym, v] }}
end

我原本期望得到以下的输出:
{:a=>1, :b=>2, :c=>3, :d=>4}

相反,我得到了:
[[[:a, nil], [:"1", nil]], [[:b, nil], [:"2", nil]], [[:c, nil], [:"3", nil]], [[:d, nil], [:"4", nil]]]

如您所见,它正在创建8个由4个符号组成的单独字符串。我无法弄清楚如何使Ruby识别这些数字并将它们设置为键/值对中的值。我已经在网上搜索过,甚至向同事寻求帮助,但到目前为止还没有找到答案。有人能帮忙吗?

5个回答

4

试试这个,我认为它看起来更干净一些。

s= "a=1, b=2, c=3, d=4"
Hash[s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}]

这里是内部工作原理。
#utilize scan with capture groups to produce a multidimensional Array
s.scan(/(\w)=(\d)/)
#=> [["a", "1"], ["b", "2"], ["c", "3"], ["d", "4"]]
#pass the inner Arrays to #map an replace the first item with a sym and the second to Integer
.map{|a,b| [a.to_sym,b.to_i]}
#=> [[:a, 1], [:b, 2], [:c, 3], [:d, 4]]
#Wrap the whole thing in Hash::[] syntax to convert
Hash[s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}]
#=>  {:a=>1, :b=>2, :c=>3, :d=>4}

如果你想避免使用我一直认为很丑陋的Hash::[]方法,可以采用以下方法:

#Ruby >= 2.1 you can use Array#to_h
  s.scan(/(\w)=(\d)/).map{|a,b| [a.to_sym,b.to_i]}.to_h
  #=>  {:a=>1, :b=>2, :c=>3, :d=>4}
#Ruby < 2.1 you can use Enumerable#each_with_object
  s.scan(/(\w)=(\d)/).each_with_object({}){|(k,v),obj| obj[k.to_sym] = v.to_i}
  #=>  {:a=>1, :b=>2, :c=>3, :d=>4}

虽然很明显这个问题有很多其他处理方法,正如在这里的许多其他答案中所表现的那样,但还是有一个更有趣的方法。

Hash[*s.scan(/(\w)=(\d)/).flatten.each_with_index.map{|k,i| i.even? ? k.to_sym : k.to_i}]

同意,scan 函数几乎肯定是更好的选择,而且更加健壮。 - Dave Newton
{}.tap{ |hash| string.scan(regexp).each{ |k,v| hash[k.to_sym] = v } } {}.tap{ |哈希| 字符串.scan(正则表达式).each{ |k,v| 哈希[k.to_sym] = v } } - SHS
1
@SyedHumzaShah,那也可以工作,除了它应该是v.to_i,虽然如果我要这样做,我会避免使用tapping,而只是使用string.scan(/(\w)=(\d)/).each_with_object({}){|(k,v),obj| obj[k.to_sym] = v.to_i}将其添加到我的帖子中。 - engineersmnky
点赞了你的评论。感谢你向我介绍了“each_with_object”。 - SHS

2
> s = "a=1, b=2, c=3, d=4"
=> "a=1, b=2, c=3, d=4"
> Hash[s.split(",").map(&:strip).map { |p| p.split("=") }.map { |k, v| [ k.to_sym, v.to_i ] }]
=> {:a=>1, :b=>2, :c=>3, :d=>4}

问题的一部分在于您试图在单行中完成它,并失去了对中间值的跟踪。将其拆分为每个组件,确保使用 Ruby 给您的内容等。
您的命名假设您会收到一个数组(而不是哈希表)。Hash[...]将基于[key, value]对的数组创建哈希表。这样可以避免手动填充哈希表。此外,该方法应返回哈希表而不是设置某些内容-保持方法小且纯净。
请注意,我修剪了第一组分割值。这样可以避免像:" a"这样的符号,如果不修剪前导/尾随空格,则会出现这种情况。我的代码没有考虑类似"a = 1"的字符串-您的代码应该考虑。
首先,使事情可读。然后,如果(仅当)它有意义并且仍然易读时,请进行代码压缩。
> s = "a=1, b=2, c=3, d=4"
=> "a=1, b=2, c=3, d=4"
> a1 = s.split(",")
=> ["a=1", " b=2", " c=3", " d=4"]
> a2 = a1.map(&:strip)
=> ["a=1", "b=2", "c=3", "d=4"]
> a3 = a2.map { |s| s.split("=") }
=> [["a", "1"], ["b", "2"], ["c", "3"], ["d", "4"]]
> a4 = a3.map { |k, v| [ k.to_sym, v.to_i ] }
=> [[:a, 1], [:b, 2], [:c, 3], [:d, 4]]
> Hash[a4]
=> {:a=>1, :b=>2, :c=>3, :d=>4}

虽然与下面要翻译的内容无关,但如果你在使用Ruby进行大量ETL(抽取、转换和加载)操作,尤其是对纯文本进行操作,使用mixins可以使代码更加清晰,接近DSL(领域特定语言)。你也可以玩一些奇怪的游戏,比如:

> def splitter(sym, s)
    String.send(:define_method, sym) do
      split(s).map(&:strip)
    end
  end
> s = "a=1, b=2, c=3, d=4"
> splitter :split_comma, ","
> splitter :split_eq,    "-"
> Hash[s.split_comma.map(&:split_eq).map { |k, v| [ k.to_sym, v.to_i ]}]
=> {:a=>1, :b=>2, :c=>3, :d=>4}

它可以变得比这更糟,成为一个完整的ETL DSL。如果需要,这是非常好的。


0
有点hackhish,但它能用,你可以从这里开始 :-)
h = {}
"a=1, b=2, c=3, d=4".split(',').each do |fo|
  k =  fo.split('=').first.to_sym 
  v =  fo.split('=').last
  h[k] = v
end

puts h.class.name
puts h

0

我的解决方案:

string = "a=1, b=2, c=3, d=4"
hash = {}
string.split(',').each do |pair|
    key,value = pair.split(/=/)
      hash[key] = value
end
puts hash.inspect

尽管它不是一行代码,但这是一个易读的解决方案。


0

你试过 Hash['a',1,'b',2,'c',3] 吗?

在你的 irb 终端上,它应该会输出这个结果 => {"a"=>1, "b"=>2, "c"=>3}

所以你需要做的就是将字符串分割并传递给 Hash,它会为你完成任务。

Hash[s.split(",").map(&:strip).map{|p| x = p.split("="); [x[0].to_sym, x[1]]}]

希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接