在特定键上查找哈希数组中的重复项

时间:2014-10-22 17:54:04

标签: ruby arrays csv hash duplicates

我有一个哈希数组(实际上是CSV行),我需要查找并保留与两个特定键(用户,部分)匹配的所有行。以下是数据样本:

[
  { user: 1, role: "staff", section: 123 },
  { user: 2, role: "staff", section: 456 },
  { user: 3, role: "staff", section: 123 },
  { user: 1, role: "exec", section: 123 },
  { user: 2, role: "exec", section: 456 },
  { user: 3, role: "staff", section: 789 }
]

所以我需要返回的是一个数组,其中只包含同一个用户/部分组合出现多次的行,如下所示:

[
  { user: 1, role: "staff", section: 123 },
  { user: 1, role: "exec", section: 123 },
  { user: 2, role: "staff", section: 456 },
  { user: 2, role: "exec", section: 456 }
]

我正在尝试的双循环解决方案看起来像这样:

enrollments.each_with_index do |a, ai|
  enrollments.each_with_index do |b, bi|
    next if ai == bi

    duplicates << b if a[2] == b[2] && a[6] == b[6]
  end
end

但由于CSV为145K行,因此永远

如何更有效地获得我需要的输出?

2 个答案:

答案 0 :(得分:8)

在效率方面,你可能想尝试一下:

grouped = csv_arr.group_by{|row| [row[:user],row[:section]]}
filtered = grouped.values.select { |a| a.size > 1 }.flatten

第一个语句按:user:section键对记录进行分组。结果是:

{[1, 123]=>[{:user=>1, :role=>"staff", :section=>123}, {:user=>1, :role=>"exec", :section=>123}],
 [2, 456]=>[{:user=>2, :role=>"staff", :section=>456}, {:user=>2, :role=>"exec", :section=>456}],
 [3, 123]=>[{:user=>3, :role=>"staff", :section=>123}],
 [3, 789]=>[{:user=>3, :role=>"staff", :section=>789}]}

第二个语句只选择具有多个成员的组的值,然后展平结果以便为您提供:

[{:user=>1, :role=>"staff", :section=>123},
 {:user=>1, :role=>"exec", :section=>123},
 {:user=>2, :role=>"staff", :section=>456},
 {:user=>2, :role=>"exec", :section=>456}]

这可以提高你的操作速度,但记忆明智我无法说出大输入的效果是什么,因为它取决于你的机器,资源和文件的大小

答案 1 :(得分:0)

为了做到这一点,在内存中检查你不需要双循环,你可以保留一组唯一值并检查每个新的csv行:

found = []
unique_enrollments = []

CSV.foreach('/path/to/csv') do |row|
  # do whatever you're doing to parse this row into the hash you show in your question:
  # => { user: 1, role: "staff", section: 123 }
  # you might have to do `next if row.header_row?` if the first row is the header

  enrollment = parse_row_into_enrollment_hash(row)
  unique_tuple = [enrollment[:user], enrollment[:section]]

  unless found.include? unique_tuple
    found << unique_tuple
    unique_enrollments << enrollment
  end
end

现在你有unique_enrollments。使用这种方法,您可以逐行解析CSV,这样您就无法将整个内容保留在内存中。然后构建一个由用户和部分组成的较小的唯一元组数组,用于进行唯一性检查,并构建唯一行数组。

您可以通过不将unique_enrollments保存在一个大数组中来进一步优化它,而只需构建模型并将其保存到db:

unless found.include? unique_tuple
  found << unique_tuple
  Enrollment.create enrollment
end

通过上述调整,您可以通过不保留大量注册来节省内存。虽然缺点是,如果事情爆发,你将无法回滚。例如,如果我们完成了前者并在最后保留了unique_enrollments的数组:

Enrollment.transaction do
  unique_enrollments.each &:save!
end

现在你有能力回滚,如果任何这些节省爆炸。此外,将一堆db调用包装在一个transaction中要快得多。我采用这种方法。

修改:使用unique_enrollments数组,您可以在最后迭代这些数组并创建新的CSV:

CSV.open('path/to/new/csv') do |csv|
  csv << ['user', 'role', 'staff'] # write the header

  unique_enrollments.each do |enrollment|
    csv << enrollment.values # just the values not the keys
  end
end