【问题标题】:How do I update single hash value where key appears multiple times in ruby?如何更新键在 ruby​​ 中多次出现的单个哈希值?
【发布时间】:2020-02-21 02:06:23
【问题描述】:

我有一个嵌套哈希,我正试图按名称排序。

pigeon_data = {
  :color => {
    :purple => ["Theo", "Peter Jr.", "Lucky"],
    :grey => ["Theo", "Peter Jr.", "Ms. K"],
    :white => ["Queenie", "Andrew", "Ms. K", "Alex"],
    :brown => ["Queenie", "Alex"]

所以我正在寻找类似的东西

 "Theo" => {
    :color => ["purple", "grey"],
    :gender => ["male"],
    :lives => ["Subway"]
  },
  "Peter Jr." => {
    :color => ["purple", "grey"],
    :gender => ["male"],
    :lives => ["Library"]
  },

但是每次我尝试更改其中一个值时,它最终都会更改具有相同键的所有值。

{"Theo"=>
  {:color=>["purple", "grey"],
 "Peter Jr."=>
  {:color=>["purple", "grey"],
  ...    

我的代码一团糟,但这里是更早的 代码已经将七只鸟作为顶级键放入嵌套哈希中 我认为问题出在附近

def sort_birds(new_sort_1)
  new_sort_2 = Marshal.load(Marshal.dump(new_sort_1))
    new_sort_1.each do |ka,va|
      va.each do |kb,vb|
        vb.each do |kc,vc|
          #binding.pry
          if vc.include?("#{ka}") && new_sort_2[ka][kb].is_a?(Array)
            new_sort_2["#{ka}"][kb] << "#{kc}"
          elsif vc.include?("#{ka}")
            new_sort_2["#{ka}"][kb] = Array.new
            new_sort_2["#{ka}"][kb] << "#{kc}"
          else

【问题讨论】:

  • 请给出前数据和结束数据的完整例子。
  • 元帅带着转储出现在这里真的很奇怪。你只是想对结构进行深度克隆吗?也可以代替"#{ka}"ka。不需要额外的引号。如果您想对它们的键进行字符串化,请明确说明:ka.to_s.
  • 抱歉,我不能提交比pigeon_data = { :color =&gt; { :purple =&gt; ["Theo", "Peter Jr.", "Lucky"], :grey =&gt; ["Theo", "Peter Jr.", "Ms. K"], :white =&gt; ["Queenie", "Andrew", "Ms. K", "Alex"], :brown =&gt; ["Queenie", "Alex"] }, :gender =&gt; { :male =&gt; ["Alex", "Theo", "Peter Jr.", "Andrew", "Lucky"], :female =&gt; ["Queenie", "Ms. K"] }, :lives =&gt; { "Subway" =&gt; ["Theo", "Queenie"], "Central Park" =&gt; ["Alex", "Ms. K", "Lucky"], "Library" =&gt; ["Peter Jr."], "City Hall" =&gt; ["Andrew"] } }更多的代码
  • 希望将所有 7 只鸟的名字都作为顶级散列来指向它们的颜色、性别和生活
  • 我有一些关于如何改进您的问题的建议(但现在改变它为时已晚):1)使您的示例中的所有输入有效 Ruby 对象,部分以便读者可以剪切- 并粘贴而无需调试。例如,pigeon_data 缺少两个右大括号; 2) 当您给出一个示例时,always 将所需的返回值显示为一个有效的 Ruby 对象。这里只显示部分返回值(仅限'Theo''Peter Jr.')并引入了不知从何而来的新元素(:gender:lives)...

标签: ruby hash key key-value


【解决方案1】:
{ color: pigeon_data[:color].flat_map { |k,v| [k].product(v) }.
  each_with_object({}) { |(color,bird),h| (h[bird] ||= []) << color.to_s } }
  #=> {:color=>{"Theo"=>["purple", "grey"],
  #             "Peter Jr."=>["purple", "grey"],
  #             "Lucky"=>["purple"],
  #             "Ms. K"=>["grey, "white"],
  #             "Queenie"=>["white, "brown"],
  #             "Andrew"=>["white"],
  #             "Alex"=>["white", "brown"]}} 

步骤如下:

a = pigeon_data[:color].flat_map { |k,v| [k].product(v) }
  #=> [[:purple, "Theo"], [:purple, "Peter Jr."], [:purple, "Lucky"],
  #    [:grey, "Theo"], [:grey, "Peter Jr."], [:grey, "Ms. K"],
  #    [:white, "Queenie"], [:white, "Andrew"], [:white, "Ms. K"],
  #    [:white, "Alex"], [:brown, "Queenie"], [:brown, "Alex"]] 
b = a.each_with_object({}) { |(color,bird),h| (h[bird] ||= []) << color.to_s }
  #=> {"Theo"=>["purple", "grey"], "Peter Jr."=>["purple", "grey"],
  #    "Lucky"=>["purple"], "Ms. K"=>["grey", "white"], "Queenie"=>["white", "brown"],
  #    "Andrew"=>["white"], "Alex"=>["white", "brown"]} 
{ color: b }
  #=> <as above>

也可以将each_with_object({}) 替换为each_with_object(Hash.new { |h,k| h[k] = [] }),将(h[bird] ||= []) 替换为h[bird]

请参阅 Enumerable#flat_mapArray#product

【讨论】:

  • 或者我们可以选择pigeon_data[:color].flat_map { |k,v| [k].product(v) }.group_by(&amp;:pop).transform_values(&amp;:flatten)
  • @engineersmnky,真的。我承认each_with_object(&lt;empty hash&gt;)group_by 的风格偏见,就像这里一样,两者都完成了工作。
【解决方案2】:

鉴于提供的结构:

pigeon_data = { :color => { 
    :purple => ["Theo", "Peter Jr.", "Lucky"], 
    :grey => ["Theo", "Peter Jr.", "Ms. K"],   
    :white => ["Queenie", "Andrew", "Ms. K", "Alex"], 
    :brown => ["Queenie", "Alex"] }, 
  :gender => {  
    :male => ["Alex", "Theo", "Peter Jr.", "Andrew", "Lucky"], 
    :female => ["Queenie", "Ms. K"] }, 
  :lives => {  
    "Subway" => ["Theo", "Queenie"], 
    "Central Park" => ["Alex", "Ms. K", "Lucky"], 
    "Library" => ["Peter Jr."], 
    "City Hall" => ["Andrew"] } }

我们可以使用基于默认进程构建的哈希循环遍历集合。

builder = Hash.new {|h,k| h[k] = Hash.new {|h2,k2| h2[k2] = []}}
pigeon_data.each_with_object(builder) do |(category,values),cage| 
  values.each do |cat_value,birds| 
    birds.each do |bird|
      cage[bird][category] << cat_value
    end  
  end 
end

当构建器收到一个新键时,它会分配一个新的哈希值作为值。当这个嵌套哈希接收到一个新键时,它会分配一个空数组作为值。那么我们只需按照我们希望它们出现的顺序放置项目[bird][category] &lt;&lt; value

导致:

    {"Theo"=>{
       :color=>[:purple, :grey], 
       :gender=>[:male], 
       :lives=>["Subway"]}, 
     "Peter Jr."=>{
       :color=>[:purple, :grey], 
       :gender=>[:male], 
       :lives=>["Library"]}, 
     "Lucky"=>{
       :color=>[:purple], 
       :gender=>[:male], 
       :lives=>["Central Park"]}, 
     "Ms. K"=>{
       :color=>[:grey, :white], 
       :gender=>[:female], 
       :lives=>["Central Park"]}, 
     "Queenie"=>{
       :color=>[:white, :brown], 
       :gender=>[:female], 
       :lives=>["Subway"]}, 
     "Andrew"=>{
       :color=>[:white], 
       :gender=>[:male], 
       :lives=>["City Hall"]}, 
     "Alex"=>{
       :color=>[:white, :brown],
       :gender=>[:male], 
       :lives=>["Central Park"]}} 

【讨论】:

  • 这行得通,但我该如何获取和排序所有三个值。
【解决方案3】:

您要做的是创建一个全新的结构,而不是试图将现有结构的副本拼凑成一个全新的形式。 Ruby 将转换定义为一系列新对象,而不是对同一对象进行就地修改。

这可以解释为:

def repigeonize(data)
  # Create a target structure for this data that's a Hash with a default...
  result = Hash.new do |h,k|
    # ...inner hash that has...
    h[k] = Hash.new do |ih, ik|
      # ... arrays assigned by default to its keys.
      ih[ik] = [ ]
    end
  end

  # Iterate over the data starting at the top level where attributes...
  data.each do |attr, set|
    # ...have keys that represent values...
    set.each do |value, names|
      # ...and list the names of those with those properties.
      names.each do |name|
        result[name][attr] << value.to_s # Converted to a string.
      end
    end
  end

  # Pass the result back
  result
end

它的工作原理如下:

pigeon_data = {
  color: {
    purple: ["Theo", "Peter Jr.", "Lucky"],
    grey: ["Theo", "Peter Jr.", "Ms. K"],
    white: ["Queenie", "Andrew", "Ms. K", "Alex"],
    brown: ["Queenie", "Alex"]
  },
  lives: {
    library: ["Peter Jr."],
    cellar: ["Queenie","Alex"],
    attic: ["Lucky","Ms. K"]
  }
}

p repigeonize(pigeon_data)
# => {"Theo"=>{:color=>["purple", "grey"]}, "Peter Jr."=>{:color=>["purple", "grey"], :lives=>["library"]}, "Lucky"=>{:color=>["purple"], :lives=>["attic"]}, "Ms. K"=>{:color=>["grey", "white"], :lives=>["attic"]}, "Queenie"=>{:color=>["white", "brown"], :lives=>["cellar"]}, "Andrew"=>{:color=>["white"]}, "Alex"=>{:color=>["white", "brown"], :lives=>["cellar"]}}

【讨论】:

  • 这是完美的,感谢您的详细解释。让我意识到我在杂草中走了多远,我为自己努力做到了。
  • 我不明白为什么你不只是获取每个属性的每个值而不检查它们是否已经包含它们中的任何一个。
  • 我不太清楚你的意思。那是在问“如何避免列表中的重复项?”或者是其他东西? Hash.new 块构造函数的好处是它只发生在如果没有填充键时,所以它第一次创建空结构。第二次结构已经存在所以它被使用了。
  • 关于 Ruby 需要牢记的一点是,简单问题的解决方案几乎总是看起来很简单。您在原始示例中的代码可能最终需要付出更多的汗水和努力,但从视觉上看很明显它没有走上正确的轨道。与其试图用武力解决问题,不如尝试将您的 Ruby 代码视为一系列链接在一​​起或整齐嵌套的较小操作。充分利用 if 实际返回值的方式,以尽量减少您必须编写的代码量。
  • 是的,这就是我所说的,但是现在一旦您将其称为块构造函数,我就会更好地理解。显然我才刚刚开始,但我之前已经了解了块构造函数。在此之前我没有在红宝石中使用它们。再次感谢。
猜你喜欢
  • 2015-09-15
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-05
相关资源
最近更新 更多