根据出现时间整理唯一元素的值 Ruby

Organize values of unique elements based on occurrence time Ruby

请帮忙解决这个问题。我有下面的 2 个数组。数组 a 包含小时数,数组 b 包含相同的小时数,然后是在这些小时发生的值。

a = ["1015","1240","1732"]
b = ["1015","X|2","D|5","1240","B|11","F|8","X|7","1732","D|9","X|1","B|3"]

所以在数组 b 中:

元素 "X|2","D|5" 发生在 10:15

小时

元素 "B|11","F|8","X|7" 发生在 12:40

小时

元素 "D|9","X|1","B|3" 发生在 17:32

小时

B 中每个元素的第一部分可以重复,例如,X 发生在 3 小时内,具有不同的值,因此在输出中,我想打印小时数和唯一值, 这是 X、D、B 和 F

我正在寻找的输出是:

HOUR    X    D    B    F 
1015    2    5
1240    7         11   8
1732    1    9    3

我目前的代码如下,但我仍然无法按所需顺序组织输出。

val=[]
headers=[]
b.each{|v|
if v.include? "|"
    headers << v.split("|")[0]
    val << v.split("|")[1]
else
    val << ["HOUR",v]
end
}

puts ["HOURS",headers.uniq].join(" ")
puts val

我的代码的当前输出:

HOURS X D B F

HOUR
1015
2
5
HOUR
1240
11
8
7
HOUR
1732
9
1
3

我不太确定您是否需要 a 数组,因为它的所有值也出现在 b 数组中 - 我将它从我的代码中删除。

它在第一步中做了什么:它将原始数组缩减为一个新数组,它将小时和其中发生的事情组合成子数组。我通过检查当前值是否为数字来执行此操作,如果不是,我将一个新操作写入当前时间 - 同时,我以正确的顺序跟踪所有可能的列。

columns = ["HOUR"]
merged = b.reduce([]) do |accumulator, value| 
  if value =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
    accumulator.push({"HOUR" => value})
  else
    parts = value.split('|')
    columns.push(parts[0]) unless columns.include?(parts[0])
    accumulator[-1][parts[0]] = parts[1]
  end
  accumulator
end

merged 现在是 [{"HOUR"=>"1015", "X"=>"2", "D"=>"5"}, {"HOUR"=>"1240", "B"=>"11", "F"=>"8", "X"=>"7"}, {"HOUR"=>"1732", "D"=>"9", "X"=>"1", "B"=>"3"}] - 而 columns 现在是 ["HOUR", "X", "D", "B", "F"]

从那里开始,我们可以准备类似 csv 的数据:

csv_like = [columns] + merged.map { |dataset| columns.map { |column| dataset.fetch(column, nil) } }

csv_like 现在是 [["HOUR", "X", "D", "B", "F"], ["1015", "2", "5", nil, nil], ["1240", "7", nil, "11", "8"], ["1732", "1", "9", "3", nil]]

这应该是您要搜索的内容 - 您现在可以使用此数据轻松创建 CSV 或 HTML table。

我假设 a 仅包含 b 中的时间,已排序。由于可以计算,因此无需提供该信息作为输入。

代码

def print_table(data, time_label, column_spacing)
  h = data.slice_before { |s| !s.include?('|') }.
           each_with_object({}) { |(t,*a),h|
             h[t] = a.map { |s| s.split('|') }.to_h.tap { |g| g.default = '' } }
  row_labels = h.keys.sort
  column_labels = h.values_at(*row_labels).reduce([]) { |a,g| a | g.keys }
  image = [[time_label, *column_labels],
          *row_labels.map { |time| [time, *h[time].values_at(*column_labels)] }]
  row_label_width, *column_widths = image.transpose.map { |r| r.map(&:size).max }
  print_image(image, row_label_width, column_widths, column_spacing)
end

def print_image(image, row_label_width, column_widths, column_spacing)
  image.each do |time, *values|
    print time.ljust(row_label_width)
    values.zip(column_widths).each { |s,width| print s.rjust(width + column_spacing) }
    puts
  end
end

例子

b = ["1240", "B|11", "F|8", "X|7",
     "1015", "X|2",  "D|5",
     "1732", "D|9",  "X|1", "B|3"]
time_label = "HOUR"
column_spacing = 2

print_table(b, time_label, column_spacing)

打印

HOUR  X  D   B  F
1015  2  5
1240  7     11  8
1732  1  9   3

请注意 b 中的时间未按排序顺序排列。

说明

对于 Example 部分中的值,第一步是将数组 b 的元素按时间分组(数组) .

groups = b.slice_before { |s| !s.include?('|') }
  #=> #<Enumerator: #<Enumerator::Generator:0x000000022b2490>:each>

参见 Enumerable#slice_before。我们可以通过将其转换为数组来查看此枚举器将生成​​的对象。

 groups.to_a
   #=> [["1240", "B|11", "F|8", "X|7"],
   #    ["1015", "X|2", "D|5"],
   #    ["1732", "D|9", "X|1", "B|3"]]

接下来,让我们将 groups 转换为散列。

h = groups.each_with_object({}) { |(t,*a),h|
  h[t] = a.map { |s| s.split('|') }.
           to_h.
           tap { |g| g.default = '' } }
  #=> {"1240"=>{"B"=>"11", "F"=>"8", "X"=>"7"},
  #    "1015"=>{"X"=>"2", "D"=>"5"},
  #    "1732"=>{"D"=>"9", "X"=>"1", "B"=>"3"}}

参见 Enumerable#each_with_object, Array#to_h, Object#tap and Hash#default=g.default = '' 为散列分配一个空的默认值 space。这意味着如果 g 没有键 kg[k] returns 一个空的 space。例如,h["1015"]["B"] #=> ""g.default = '' returns '',这就是为什么它被包含在 tap 块中的原因,其中 returns g 具有默认定义。

This article 提供了 splat 运算符的用法说明。 (在这里,简而言之:[1, *[2, 3]] #=> [1, 2, 3])。

对于列标签,我们有几个选项。无论如何,我们首先需要 h 的值(散列)中的唯一键对应于 row_labels.

中的键
row_labels = h.keys.sort
  #=> ["1015", "1240", "1732"]
column_labels = h.values_at(*row_labels)
  #=> [{"X"=>"2", "D"=>"5"},
  #    {"B"=>"11", "F"=>"8", "X"=>"7"},
  #    {"D"=>"9", "X"=>"1", "B"=>"3"}]
column_labels = column_labels.reduce([]) { |a,g| a | g.keys }
  #=> ["X", "D", "B", "F"]

参见 Enumerable#values_at, Enumerable#reduce (aka inject) and Array#|。我假设这给出了所需的列顺序,但是 column_labels 的元素可以根据需要重新排序。我在答案的最后部分提出了两个可能的选择。

我们接下来构建一个数组,其中包含要打印的 table 中的所有值。

image = [[time_label, *column_labels],
          *row_labels.map { |time| [time, *h[time].values_at(*column_labels)] }]
  #=> [["HOUR", "X", "D", "B", "F"],
  #    ["1015", "2", "5", "", ""],
  #    ["1240", "7", "", "11", "8"],
  #    ["1732", "1", "9", "3", ""]]

Enumerable#values_at 提取 h[time] 中与 table 的每一行对应的值(散列),并按所需顺序排列。

然后我们可以打印 table 如下。

row_label_width, *column_widths = image.transpose.map { |r| r.map(&:size).max }
  # => [4, 1, 1, 2, 1]

所以

row_label_width
  #=> 4
column_widths
  #=> [1, 1, 2, 1]

image.each do |time, *values|
  print time.ljust(row_label_width)
  values.zip(column_widths).each { |s,width| print s.rjust(width + column_spacing) }
  puts
end

打印 示例 部分中显示的 table。

列顺序

正如我之前所说,column_labels 的元素可以根据需要重新排序。一种可能性是按字母顺序对标签进行排序。

column_labels = h.values_at(*row_labels).reduce([]) { |a,g| a | g.keys }.sort!
  #=> ["B", "D", "F", "X"]

另一个是我们得到了所有可能的列标签的所需顺序。

desired = ["Y", "F", "D", "Z", "B", "X"]

然后计算以下内容。

column_labels = desired & h.values_at(*row_labels).reduce([]) { |a,g| a | g.keys }
  #=> ["F", "D", "B", "X"]