通过Hash迭代输出无序列表

时间:2014-12-04 17:30:00

标签: ruby-on-rails ruby hash

我有一个Hash,其中大部分都填充了一个带有两个与键关联的值的键。在这个Hash中还有另一个哈希,这是我被困住的地方。

让我们说哈希看起来像:

{'sports'=>['football', 'basketball'], 'season'=>['summer','fall'], 'data'=>[{'holiday'=>'Christmas', 'genre' => 'Comedy'}, {'holiday'=>'Thanksgiving', 'genre' => 'Action'}]}

输出应如下所示:

Sports
  - football
  - basketball
Season
  - summer
  - fall
Holiday
  - Christmas
  - Thanksgiving
Genre
  - Comedy
  - Action

到目前为止,我有一个帮助器,除了data部分之外,还有一些帮助。

def output_list_from(hash)
  return if hash.empty?

  content_tag(:ul) do
    hash.map do |key, values|
      content_tag(:li, key.to_s.humanize) +

      content_tag(:ul) do
        # if values.is_a?(Hash)...
        content_tag(:li, values.first) +
        content_tag(:li, values.last)
      end
    end.join.html_safe
  end.html_safe
end

返回输出:

Sports
  - football
  - basketball
Season
  - summer
  - fall
Data
  - {'holiday'=>'Christmas', 'genre' => 'Comedy'}
  - {'holiday'=>'Thanksgiving', 'genre' => 'Action'}

当然这是有道理的......所以我试图在循环中检查value是否是哈希,但是它的设置方式欺骗了我。我认为如果我知道哈希每次都会是什么样子会更容易,但每次都会是一个新的哈希值。一次可能有holiday个数据,另一次可能有holidaygenre

任何建议都将受到赞赏。

2 个答案:

答案 0 :(得分:2)

您需要使用正确的格式创建哈希。像这样:

hash = {'sports'=>['football', 'basketball'], 'season'=>['summer','fall'], 'data'=>[{'holiday'=>'Christmas', 'genre' => 'Comedy'}, {'holiday'=>'Thanksgiving', 'genre' => 'Action'}]}
formatted_data = hash.dup

data = formatted_data.delete('data')
if data
  data.each do |item|
    item.each do |k, v| 
      formatted_data[k] ||= []
      formatted_data[k] << v
    end
  end
end

puts formatted_data
# => {"sports"=>["football", "basketball"], "season"=>["summer", "fall"], 
# => "holiday"=>["Christmas", "Thanksgiving"], "genre"=>["Comedy", "Action"]}

content_tag(:ul) do
  formatted_data.map do |key, values|
    #... your code here...
  end.join.html_safe
end.html_safe

答案 1 :(得分:1)

假设您的哈希看起来像这样:

hash = { 'sports'=>['football', 'basketball'],
         'season'=>['summer', 'fall'],
         'data1' =>[{ 'holiday'=>'Christmas', 'genre'=>'Comedy'},
                    { 'holiday'=>'Thanksgiving', 'genre'=>'Action' }],
         'data2' =>[{ 'data3'=>[{ 'sports'=>'darts', 'genre'=>'Occult' }] }] 
        } 

并且您想要一个适用于任意数量级别的通用解决方案,并且不依赖于结果哈希中不会出现的键的名称(此处为'data1''data2'和{ {1}})。这是使用递归的一种方法。

<强>代码

'data3'

示例

def extract(h, new_hash = {})
  h.each do |k,v|
    [*v].each do |e|
      case e
      when Hash then extract(e, new_hash)
      else new_hash.update({ k=>[e] }) { |_,ov,nv| ov << nv.first }
      end
    end
  end   
  new_hash
end

<强>解释

我认为,代码中有两件事可能需要澄清。

#1

第一个是相当孤独和奇怪的表情:

extract(hash)
  #=> {"sports"=>["football", "basketball", "darts"],
  #    "season"=>["summer", "fall"],
  #    "holiday"=>["Christmas", "Thanksgiving"],
  #    "genre"=>["Comedy", "Action", "Occult"]}

如果[*v] 是数组,则返回v。如果v是文字,则splat运算符无效,因此返回v。换句话说,它只留下数组并将文字转换为包含一个元素的数组本身。埃尔戈:

[v]

这使我们省去了[*['football', 'basketball']] #=> ["football", "basketball"] [*'Thanksgiving'] #=> ["Thanksgiving"] 语句中有三种而不是两种可能性的麻烦。我们只是将文字转换为一个元素的数组,允许我们只处理哈希和数组。

#2

可能对某些人不熟悉的第二个片段是:

case

这使用方法Hash#update(a.k.a。new_hash.update({ k=>[e] }) { |_,ov,nv| ov << nv.first } )的形式,该方法使用块来解析合并的两个哈希中存在的键的值。例如,在计算的某个阶段,merge!将具有键值对:

new_hash

并将使用散列 1

进行更新
'sports'=>['football', 'basketball']

由于这两个哈希值都具有键{ 'sports'=>['darts'] } ,因此该块被称为仲裁器:

'sport'

由于我没有在块中使用密钥{ |k,ov,nv| ov << nv.first } #=> { |'sport', ['football', 'basketball'], ['darts']| ov << nv.first } #=> { |'sport', ['football', 'basketball'], ['darts']| ['football', 'basketball'] << 'darts' } #=> ['football', 'basketball'] << 'darts' ,我已使用占位符('sport')替换了该块变量,以减少出错的机会,并告知读者密钥未被使用。

1我有时会使用飞镖作为运动的例子,因为它是少数几个可以在没有身体健康的情况下获得成功的人之一。