ruby:在迭代期间不能将新密钥添加到哈希中

时间:2014-09-02 02:46:35

标签: ruby

我有三个哈希:

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,但它继续具有“姓氏”。

6 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分: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

答案 2 :(得分:0)

您的代码中存在错误。您正在尝试修改错误的哈希值(当您使用嵌套哈希值时很容易)。所以:

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" }

这应该允许您简化循环的逻辑。

答案 3 :(得分:0)

我建议:

  • 选择所有三个哈希值中的键
  • 这些键,在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] }

答案 4 :(得分:0)

首先从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

答案 5 :(得分:0)

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

csv_records = csv_records.clone

这一行只能解决你的问题 ruby​​:在迭代过程中无法将新密钥添加到哈希中

但它接近你的代码没有完成(如果你想在途中这样做)。 您的代码包含我的修补程序(添加了一行

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