Упорядочить значения уникальных элементов по времени появления Ruby - PullRequest
0 голосов
/ 10 июня 2018

Пожалуйста, помогите в этом.У меня есть 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

Ответы [ 2 ]

0 голосов
/ 10 июня 2018

Я предположил, что 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 не в отсортированном порядке.

Пояснение

Для значений в разделе Пример первым шагом является группирование элементов массива 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 и Hash # default = .g.default = '' присваивает хешу значение по умолчанию для пустого пространства.Это означает, что g[k] возвращает пустое пространство, если g не имеет ключа k.Например, h["1015"]["B"] #=> "".g.default = '' возвращает '', поэтому он заключен в блок tap, который возвращает g с определением по умолчанию.

В этой статье дается объяснениеиспользование оператора 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 , Перечислимый # уменьшите (он же inject) и Array # | .Я предположил, что это дает желаемый порядок столбцов, но элементы column_labels могут быть переупорядочены при желании.В последнем разделе моего ответа я представлю два возможных варианта.

Далее мы создадим массив, содержащий все значения в таблице для печати.

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], которые соответствуют каждой строке таблицы, в нужном порядке.

Затем мы можем напечатать таблицу как

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

печатает таблицу, показанную в разделе Пример .

Порядок столбцов

Как я уже говорил ранее, элементы 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"]
0 голосов
/ 10 июня 2018

Я не уверен, что вам когда-нибудь понадобится массив 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 с этими данными сейчас.

...