【问题标题】:ruby: can't add a new key into hash during iterationruby:在迭代期间无法将新密钥添加到哈希中
【发布时间】:2014-09-02 02:46:35
【问题描述】:

我有三个哈希:

db_headers = {"1"=>"first_name", "2"=>"last_name"}
csv_headers = {"1"=>"First Name", "2"=>"Last Name"}
csv_records = {"0"=>{"id"=>"11", "first_name"=>"first_0", "Last Name"=>"last_0", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC"}, "1"=>{"id"=>"12", "first_name"=>"first_1", "Last Name"=>"last_1", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC"}}

db_headers 和 csv_headers 由它们的键匹配。例如,它们的键“2”值分别包含“last_name”和“Last Name”。我的目标是,只要键相同的 db_headers 和 csv_headers 之间的值不同,那么我需要将 csv_records 中的键与 db_headers 的值交换。例如,csv_records 键将从“Last Name”更改为“last_name”,因为键“2”处的 db_headers 和 csv_headers 值不同。

这是我想出的:

  csv_records.each do |record_key,record_value|
    csv_headers.each do |csv_key,csv_value|
      if record_value.has_key? csv_value
          db_headers.each do |db_key, db_value|
            if csv_key == db_key
              csv_records[db_value] = csv_records.delete csv_value
              break
            end
          end
          break
      end
    end
  end

不幸的是它失败了:

RuntimeError: can't add a new key into hash during iteration
    from (irb):12:in `[]='
    from (irb):12:in `block (3 levels) in irb_binding'
    from (irb):10:in `each'
    from (irb):10:in `block (2 levels) in irb_binding'
    from (irb):8:in `each'
    from (irb):8:in `block in irb_binding'
    from (irb):7:in `each'
    from (irb):7

这使错误消失了:

csv_records.keys.each do |record_key|
    csv_headers.keys.each do |csv_key|
      if csv_records[record_key].has_key? csv_headers[csv_key]
          db_headers.keys.each do |db_key|
            if csv_key == db_key
              csv_records[db_headers[db_key]] = csv_records.delete csv_headers[csv_key]
              # break is needed becasue csv_key wont exist in next iteration
              break
            end
          end
      end
    end
  end

但是 csv_records 现在应该有一个值 last_name,但它仍然有“姓氏”。

【问题讨论】:

    标签: ruby


    【解决方案1】:

    迭代 hash.keys 而不是 hash#keys 将创建一个与散列分开的数组,因此您在修改散列时不会弄乱迭代。

    【讨论】:

    • 如果我迭代哈希键,我应该如何检索哈希值?
    • hash[key]...?当你只有一个键时检索一个值是散列的核心功能。
    • 这并没有解决代码中的实际错误。请看我的解决方案。无需迭代 hash.keys。
    • @ClaytonC:这是正确的;我提供了处理特定运行时错误的标准方法。
    【解决方案2】:

    除非您有很大的内存限制,否则请使用reduce 来建立您想要的记录。

    # If you need to keep csv_headers and db_headers for another reason, you can use them to create REPLACE_KEYS.
    REPLACE_KEYS = {"First Name"=>"first_name", "Last Name"=>"last_name"}
    csv_records = {"0"=>{"id"=>"11", "first_name"=>"first_0", "Last Name"=>"last_0", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC"}, "1"=>{"id"=>"12", "first_name"=>"first_1", "Last Name"=>"last_1", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC"}}    
    
    def transform_record(record)
      record.reduce({}) do |acc, (key, value)|
        new_key = REPLACE_KEYS[key] || key
        acc[new_key] = value
        acc
      end
    end
    
    db_records = csv_records.reduce({}) do |acc, (row, record)|
      acc[row] = transform_record(record)
      acc
    end
    

    【讨论】:

    • 我发现这很有用
    【解决方案3】:

    您的代码中存在错误。您正在尝试修改错误的哈希(当您有嵌套哈希时很容易做到这一点)。所以:

    csv_records[db_value] = csv_records.delete csv_value
    

    应该是:

    record_value[db_value] = record_value.delete csv_value
    

    仅此一项就可以解决您的问题。

    另外,作为进一步的提示,您似乎可以将 csv_headers 和 db_headers 哈希组合成一个哈希:

    { "First Name" => "first_name",
    "Last Name" => "last_name" }
    

    这应该可以让您简化循环的逻辑。

    【讨论】:

      【解决方案4】:

      我建议:

      • 选择所有三个哈希中的键
      • 在这些键中,选择在db_headerscsv_headers 中具有不同值的键
      • 对于这些键,交换db_headerscsv_headers 中的值

      将这种方法转换为代码很简单:

      (csv_records.keys & db_headers.keys & csv_headers.keys).select { |k|
        db_headers[k] != csv_headers[k] }.each { |k|
          db_headers[k], csv_headers[k] = csv_headers[k], db_headers[k] }
      
      db_headers  #=> {"1"=>"First Name", "2"=>"last_name"}
      csv_headers #=> {"1"=>"first_name", "2"=>"Last Name"}
      

      我们有

      keys = csv_records.keys & db_headers.keys & csv_headers.keys
        #=> ["1"]
      
      selected_keys = keys.select { |k| db_headers[k] != csv_headers[k] }
        #=> ["1"]
      

      然后对这些键中的每一个(这里只有一个)执行并行分配:

      selected_keys.each { |k|
        db_headers[k], csv_headers[k] = csv_headers[k], db_headers[k] }
      

      【讨论】:

        【解决方案5】:

        首先从 db_headers 和 csv_headers 中获取替换规则

        map = Hash[db_headers.merge(csv_headers){|_,v1,v2| [v2,v1]}.values]
        #=> {"First Name"=>"first_name", "Last Name"=>"last_name"}
        

        然后点击传输 csv_records 中的数据:

        csv_records.tap {|x| 
          map.each {|from,to| 
            x.each{|k,v| 
              x[k][to]=x[k][from] if x[k][from]
              x[k].delete(from) 
            }
          }
        }
        #=> {"0"=>{"id"=>"11", "first_name"=>"first_0", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC", "last_name"=>"last_0"}, "1"=>{"id"=>"12", "first_name"=>"first_1", "created_at"=>"2014-08-12 17:02:28 UTC", "updated_at"=>"2014-08-12 17:02:28 UTC", "last_name"=>"last_1"}}
        

        缩短为一行:

        csv_records.tap {|x| map.each {|from,to| x.each{|k,v| x[k][to]=x[k].delete(from) if x[k][from] }}}
        

        更新

        按照你的逻辑,问题是:

        • cvs_headers 的迭代中无需迭代 db_headers,直接从 db_hearders 哈希中获取目标 db_hearder。
        • csv_records.delete csv_headers[csv_key] 什么都不做,因为 csv_records 只有“0”和“1”键,你应该使用csv_records[record_key].delete csv_headers[csv_key]

        试试这个:

        csv_records.keys.each do |record_key|
          csv_headers.keys.each do |csv_key|
            if csv_records[record_key].has_key? csv_headers[csv_key]
              csv_records[record_key][db_headers[csv_key]] = csv_records[record_key].delete csv_headers[csv_key] if csv_records[record_key][csv_headers[csv_key]]
            end
          end
        end
        
        csv_records
        

        【讨论】:

          【解决方案6】:

          只需将此行添加到您的代码中,您必须首先克隆您的哈希。

          csv_records = csv_records.clone
          

          这一行将仅解决您使用 ruby: can't add a new key into hash during iteration

          的问题

          但它看起来就像你的代码没有完成(如果你想按自己的方式做)。 您的代码和我的修复(添加了一行

          csv_records.each do |record_key,record_value|
            csv_headers.each do |csv_key,csv_value|
              if record_value.has_key? csv_value
                db_headers.each do |db_key, db_value|
                  if csv_key == db_key
                    csv_records = csv_records.clone
                    csv_records[db_value] = csv_records.delete csv_value
                    break
                  end
                end
                break
              end
            end
          end
          

          【讨论】:

          • 嗨,欢迎来到 StackOverflow!您能否提供一些信息或解释您的解决方案如何解决作者的问题?
          • 一个改变的答案来解释如何解决它
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-05-25
          • 1970-01-01
          • 2016-04-07
          • 2011-11-15
          • 2014-10-01
          • 2015-04-19
          • 1970-01-01
          相关资源
          最近更新 更多