Ruby форматирование данных - PullRequest
0 голосов
/ 28 июня 2018

Я читаю файл журнала и пытаюсь упорядочить данные в следующем формате, поэтому я хотел нажать NAME (то есть USOLA51, USOLA10 ..) в качестве хеша и создать соответствующий массив для LIST и Подробнее 1004 *. Я тоже создал хеш, но не уверен, как получить / извлечь соответствующие / связанные значения массива.

Ожидаемый результат

NAME           LIST             DETAILS

USOLA51        ICC_ONUS         .035400391
               PA_ONUS          .039800391
               PA_ONUS          .000610352

USOLA10        PAL               52.7266846
              CFG_ONUS           15.9489746
likewise for the other values

Файл журнала:

--- data details ----

USOLA51

ONUS                    size
------------------------------ ----------
ICC_ONUS               .035400391
PA_ONUS            .039800391
PE_ONUS            .000610352

=========================================


---- data details ----


USOLA10


ONUS                    size
------------------------------ ----------
PAL                52.7266846
CFG_ONUS               15.9489746


=========================================

---- data details ----


USOLA55


ONUS                    size
------------------------------ ----------
PA_ONUS            47.4707031
PAL              3.956604
ICC_ONUS               .020385742
PE_ONUS            .000610352


=========================================


---- data details ----

USOLA56

ONUS                    size
------------------------------ ----------

=========================================

что я пробовал

unique = Array.new
owner = Array.new
db = Array.new
File.read("mydb_size.log").each_line do |line|
  next if line =~ /---- data details ----|^ONUS|---|=======/   
  unique << line.strip if line =~ /^U.*\d/ 

end

hash = Hash[unique.collect { |item| [item, ""] } ]

puts hash

Ток O / p

{"USOLA51"=>"", "USOLA10"=>"", "USOLA55"=>"", "USOLA56"=>""}

Любая помощь для продвижения вперед будет очень полезна здесь. Спасибо !!

Ответы [ 2 ]

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

Хотя ваш файл журнала не CSV, я нахожу библиотеку csv полезной во многих случаях, не связанных с CSV. Вы можете использовать его для анализа файла журнала, пропустив пустые строки и любую строку, начинающуюся с --- , === или ONUS . В качестве разделителя столбцов используется пробел:

csv = CSV.read("./example.log", skip_lines: /\A(---|===|ONUS)/,
               skip_blanks: true, col_sep: " ")

Тогда в некоторых строках разбирается только 1 элемент в массиве, это строки заголовка. Таким образом, мы можем разбить массив csv на группы, основываясь на том, что у нас есть только 1 элемент, и создать хеш из результата:

output_hash = csv.slice_before { |row| row.length == 1 }.
  each_with_object({}) do |((name), *rows), hash|
  hash[name] = rows.to_h
end

Теперь немного сложно сказать, хотите ли вы, чтобы вывод хеша отображался как текст, который вы показали, или вы просто хотели получить хеш. Если вы хотите вывод текста, сначала нам нужно увидеть, сколько места нужно отобразить в каждом столбце:

name_length = output_hash.keys.max_by(&:length).length
list_length = output_hash.values.flat_map(&:keys).max_by(&:length).length
detail_length = output_hash.values.flat_map(&:values).max_by(&:length).length

format = "%-#{name_length}s %-#{list_length}s %-#{detail_length}s"

и затем мы можем вывести строку заголовка и все значения в output_hash, но только если они имеют какие-либо значения:

puts("#{format}\n\n" % ["NAME", "LIST", "DETAILS"])

output_hash.reject { |name, values| values.empty? }.each do |name, values|
  list, detail = values.first
  puts(format % [name, list, detail])

  values.drop(1).each do |list, detail|
    puts(format % ['', list, detail])
  end

  puts
end

и результат:

NAME    LIST     DETAILS   

USOLA51 ICC_ONUS .035400391
        PA_ONUS  .039800391
        PE_ONUS  .000610352

USOLA10 PAL      52.7266846
        CFG_ONUS 15.9489746

USOLA55 PA_ONUS  47.4707031
        PAL      3.956604  
        ICC_ONUS .020385742
        PE_ONUS  .000610352

Немного сложно объяснить (для меня), что делает slice_before. Но он принимает массив (или другой перечисляемый) и создает группы или чанки своего элемента, где первый элемент соответствует параметру или блок возвращает true. Например, если у нас был меньший массив:

array = ["slice here", 1, 2, "slice here", 3, 4]
array.slice_before { |el| el == "slice here" }.entries
# => [["slice here", 1, 2], ["slice here", 3, 4]]

Мы сказали slice_before, что мы хотим, чтобы каждая группа начиналась с элемента, равного «срез здесь», поэтому мы возвращаем 2 группы, первый элемент в каждой «срез здесь», а остальные элементы - все элементы в массиве, пока в следующий раз не увидит «здесь».

Итак, мы можем взять этот результат и вызвать для него each_with_object, передавая пустой хеш для начала. При each_with_object первый параметр будет элементом массива (из каждого), а второй будет объектом, который вы передали. Что происходит, когда параметры блока выглядят как |((name), *rows), hash|, так это то, что первый параметр (элемент массива) деконструируется в первый элемент массива и остальные элементы:

# the array here is what gets passed to `each_with_object` for the first iteration as the first parameter
name, *rows = [["USOLA51"], ["ICC_ONUS", ".035400391"], ["PA_ONUS", ".039800391"], ["PE_ONUS", ".000610352"]]
name # => ["USOLA51"]
rows # => [["ICC_ONUS", ".035400391"], ["PA_ONUS", ".039800391"], ["PE_ONUS", ".000610352"]]

Итак, мы снова деконструируем этот первый элемент, просто чтобы у нас его не было в массиве:

name, * = name # the `, *` isn't needed in the block parameters, but is needed when you run these examples in irb
name # => "USOLA51"

Для max_by(&:length).length все, что мы делаем, это находим самый длинный элемент в массиве (возвращаемый либо keys, либо values) и получая его длину:

output_hash = {"USOLA51"=>{"ICC_ONUS"=>".035400391", "PA_ONUS"=>".039800391", "PE_ONUS"=>".000610352"}, "USOLA10"=>{"PAL"=>"52.7266846", "CFG_ONUS"=>"15.9489746"}, "USOLA55"=>{"PA_ONUS"=>"47.4707031", "PAL"=>"3.956604", "ICC_ONUS"=>".020385742", "PE_ONUS"=>".000610352"}, "USOLA56"=>{}}
output_hash.values.flat_map(&:keys)
# => ["ICC_ONUS", "PA_ONUS", "PE_ONUS", "PAL", "CFG_ONUS", "PA_ONUS", "PAL", "ICC_ONUS", "PE_ONUS"]
output_hash.values.map(&:length) # => [8, 7, 7, 3, 8, 7, 3, 8, 7]
output_hash.values.flat_map(&:keys).max_by(&:length) # => "ICC_ONUS"
output_hash.values.flat_map(&:keys).max_by(&:length).length # => 8
0 голосов
/ 28 июня 2018

Я давно работаю с ruby, поэтому, вероятно, я забыл много ярлыков и синтаксического сахара, но этот файл, кажется, легко разбирается без больших усилий.

Достаточно простого построчного сравнения ожидаемых значений. Первый шаг - удалить все окружающие пробелы, игнорировать пустые строки или строки, начинающиеся с = или -. Далее, если есть только одно значение, это заголовок, следующая строка состоит из имен столбцов, которые можно игнорировать для желаемого результата. Если встречаются имена заголовков или столбцов, перейдите к следующей строке и сохраните следующие пары ключ / значение в качестве пар ключ / значение ruby. Во время этой операции также проверьте наличие самой длинной встречающейся строки и отрегулируйте заполнение столбцов, чтобы впоследствии можно было генерировать табличный вывод с заполнением.

# Set up the loop
merged = []
current = -1
awaiting_headers = false
columns = ['NAME', 'LIST', 'DETAILS']
# Keep track of the max column length
columns_pad = columns.map { |c| c.length }

str.each_line do |line|
  # Remove surrounding whitespaces, 
  # ignore empty or = - lines
  line.strip!
  next if line.empty?
  next if ['-','='].include? line[0]
  # Get the values of this line
  parts = line.split ' '
  # We're not awaiting the headers and 
  # there is just one value, must be the title
  if not awaiting_headers and parts.size == 1
    # If this string is longer than the current maximum
    columns_pad[0] = line.length if line.length > columns_pad[0]
    # Create a hash for this item
    merged[current += 1] = {name: line, data: {}}
    # Next must be the headers
    awaiting_headers = true
    next
  end
  # Headers encountered
  if awaiting_headers
    # Just skip it from here
    awaiting_headers = false
    next
  end
  # Take 2 parts of each (should be always only those two) 
  # and treat them as key/value
  parts.each_cons(2) do |key, value|
    # Make it a ruby key/value pair
    merged[current][:data][key] = value 
    # Check if LIST or DETAILS column length needs to be raised
    columns_pad[1] = key.length if key.length > columns_pad[1]
    columns_pad[2] = value.length if value.length > columns_pad[2]
  end
end

# Adding three spaces between columns
columns_pad.map! { |c| c + 3}  

# Writing the headers
result = columns.map.with_index { |c, i| c.ljust(columns_pad[i]) }.join + "\n"

merged.each do |item|
  # Remove the next line if you want to include empty data
  next if item[:data].empty?  
  result += "\n"
  result += item[:name].ljust(columns_pad[0])
  # For the first value in data, we don't need extra padding or a line break
  padding = ""
  item[:data].each do |key, value|
    result += padding
    result += key.ljust(columns_pad[1])
    result += value.ljust(columns_pad[2])
    # Set the padding to include a line break and fill up the NAME column with spaces
    padding = "\n" + "".ljust(columns_pad[0])
  end
  result += "\n"
end

puts result

Что приведет к

NAME      LIST       DETAILS      

USOLA51   ICC_ONUS   .035400391   
          PA_ONUS    .039800391   
          PE_ONUS    .000610352   

USOLA10   PAL        52.7266846   
          CFG_ONUS   15.9489746   

USOLA55   PA_ONUS    47.4707031   
          PAL        3.956604     
          ICC_ONUS   .020385742   
          PE_ONUS    .000610352   

Демо-версия здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...