如何 "split and group" 基于其属性之一的对象数组
How to "split and group" an array of objects based on one of their properties
上下文和代码示例
我有一个 Array,其中包含一个名为 TimesheetEntry 的 class 实例。
这是 TimesheetEntry 的构造函数:
def initialize(parameters = {})
@date = parameters.fetch(:date)
@project_id = parameters.fetch(:project_id)
@article_id = parameters.fetch(:article_id)
@hours = parameters.fetch(:hours)
@comment = parameters.fetch(:comment)
end
我使用 .csv
文件中的数据创建了一个 TimesheetEntry 对象数组:
timesheet_entries = []
CSV.parse(source_file, csv_parse_options).each do |row|
timesheet_entries.push(TimesheetEntry.new(
:date => Date.parse(row['Date']),
:project_id => row['Project'].to_i,
:article_id => row['Article'].to_i,
:hours => row['Hours'].gsub(',', '.').to_f,
:comment => row['Comment'].to_s.empty? ? "N/A" : row['Comment']
))
end
我还有一个 Set of Hash 包含两个元素,创建如下:
all_timesheets = Set.new []
timesheet_entries.each do |entry|
all_timesheets << { 'date' => entry.date, 'entries' => [] }
end
现在,我想用 TimesheetEntries 填充该哈希内的数组。
每个哈希数组必须仅包含一个特定日期的 TimesheetEntries。
我是这样做的:
timesheet_entries.each do |entry|
all_timesheets.each do |timesheet|
if entry.date == timesheet['date']
timesheet['entries'].push entry
end
end
end
虽然这种方法可以完成工作,但效率不高(我对此还很陌生)。
问题
实现相同最终结果的更有效方法是什么?本质上,我想 "split" TimesheetEntry 对象数组,"grouping" 个具有相同日期的对象。
您可以通过将 Set
替换为 Hash
来解决性能问题,这是一个类似字典的数据结构。
这意味着您的内部循环 all_timesheets.each do |timesheet| ... if entry.date ...
将简单地替换为更高效的哈希查找:all_timesheets[entry.date]
.
此外,无需提前创建密钥,然后然后填充日期组。这些都可以一次性完成:
all_timesheets = {}
timesheet_entries.each do |entry|
all_timesheets[entry.date] ||= [] # create the key if it's not already there
all_timesheets[entry.date] << entry
end
散列的一个好处是您可以在遇到不存在的键时自定义它们的行为。您可以使用带块的 constructor 来指定在这种情况下发生的情况。让我们告诉我们的散列自动添加新键并用空数组初始化它们。这允许我们从上面的代码中删除 all_timesheets[entry.date] ||= []
行:
all_timesheets = Hash.new { |hash, key| hash[key] = [] }
timesheet_entries.each do |entry|
all_timesheets[entry.date] << entry
end
然而,有一种更简洁的方法可以实现这种分组,使用 Enumerable#group_by
method:
all_timesheets = timesheet_entries.group_by { |e| e.date }
当然,还有一种方法可以使它更简洁,使用 another trick:
all_timesheets = timesheet_entries.group_by(&:date)
上下文和代码示例
我有一个 Array,其中包含一个名为 TimesheetEntry 的 class 实例。
这是 TimesheetEntry 的构造函数:
def initialize(parameters = {})
@date = parameters.fetch(:date)
@project_id = parameters.fetch(:project_id)
@article_id = parameters.fetch(:article_id)
@hours = parameters.fetch(:hours)
@comment = parameters.fetch(:comment)
end
我使用 .csv
文件中的数据创建了一个 TimesheetEntry 对象数组:
timesheet_entries = []
CSV.parse(source_file, csv_parse_options).each do |row|
timesheet_entries.push(TimesheetEntry.new(
:date => Date.parse(row['Date']),
:project_id => row['Project'].to_i,
:article_id => row['Article'].to_i,
:hours => row['Hours'].gsub(',', '.').to_f,
:comment => row['Comment'].to_s.empty? ? "N/A" : row['Comment']
))
end
我还有一个 Set of Hash 包含两个元素,创建如下:
all_timesheets = Set.new []
timesheet_entries.each do |entry|
all_timesheets << { 'date' => entry.date, 'entries' => [] }
end
现在,我想用 TimesheetEntries 填充该哈希内的数组。 每个哈希数组必须仅包含一个特定日期的 TimesheetEntries。
我是这样做的:
timesheet_entries.each do |entry|
all_timesheets.each do |timesheet|
if entry.date == timesheet['date']
timesheet['entries'].push entry
end
end
end
虽然这种方法可以完成工作,但效率不高(我对此还很陌生)。
问题
实现相同最终结果的更有效方法是什么?本质上,我想 "split" TimesheetEntry 对象数组,"grouping" 个具有相同日期的对象。
您可以通过将 Set
替换为 Hash
来解决性能问题,这是一个类似字典的数据结构。
这意味着您的内部循环 all_timesheets.each do |timesheet| ... if entry.date ...
将简单地替换为更高效的哈希查找:all_timesheets[entry.date]
.
此外,无需提前创建密钥,然后然后填充日期组。这些都可以一次性完成:
all_timesheets = {}
timesheet_entries.each do |entry|
all_timesheets[entry.date] ||= [] # create the key if it's not already there
all_timesheets[entry.date] << entry
end
散列的一个好处是您可以在遇到不存在的键时自定义它们的行为。您可以使用带块的 constructor 来指定在这种情况下发生的情况。让我们告诉我们的散列自动添加新键并用空数组初始化它们。这允许我们从上面的代码中删除 all_timesheets[entry.date] ||= []
行:
all_timesheets = Hash.new { |hash, key| hash[key] = [] }
timesheet_entries.each do |entry|
all_timesheets[entry.date] << entry
end
然而,有一种更简洁的方法可以实现这种分组,使用 Enumerable#group_by
method:
all_timesheets = timesheet_entries.group_by { |e| e.date }
当然,还有一种方法可以使它更简洁,使用 another trick:
all_timesheets = timesheet_entries.group_by(&:date)