Как разобрать строку в хеш-таблицу в Ruby - PullRequest
2 голосов
/ 23 марта 2019

Я получаю данные в виде строки с удаленного устройства. Мне нужно проанализировать данные. Данные обычно приходят так:

MO                SCGR  SC         RSITE           ALARM_SITUATION
RXOTG-59            59  0          EK0322          ABIS PATH FAULT
RXOCF-59                           EK0322          LOCAL MODE
RXOTRX-59-0         4              EK0322          LOCAL MODE
RXOTRX-59-1                        EK0322          LOCAL MODE
RXOTRX-59-4             0          EK0322          LOCAL MODE
RXOTRX-59-5         1   3          EK0322          LOCAL MODE
RXOTRX-59-8                        EK0322          LOCAL MODE
RXOTRX-59-9                        EK0322          LOCAL MODE

Мне очень хотелось бы, чтобы данные представляли собой массив массивов или любую другую программно-понятную структуру.

Я разделяю данные на массив, используя:

str.split("\r\n")

, а затем удаляем лишние пробелы в каждом элементе массива с помощью:

tsgs.map! {|tsg| tsg.gsub(/\s+/, " ").split(" ") }

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

Случай 1: В этом случае я получаю ожидаемый результат:

RXOTG-59            59  0          EK0322          ABIS PATH FAULT

конвертируется в

["RXOTG-59", "59", "0", "EK0322", "ABIS PATH FAULT"]

Случай 2: В этом случае я получаю неожиданный результат:

RXOTRX-59-9                        EK0322          LOCAL MODE

конвертируется в

["RXOTRX-59-9", "EK0322", "LOCAL MODE"]
   def getCommandResult(tgdatas)
        tgdatas_arr = tgdatas.split("\r\n")
        tsgs = tgdatas_arr[5..tgdatas_arr.index("END")-2]
        tsgs.map! {|tsg| tsg.gsub(/\s+/, " ").split(" ")[0] }
        return tsgs
    end

Ответы [ 3 ]

5 голосов
/ 23 марта 2019

String.unpack с директивой "A" подходит для строк фиксированной ширины.

str = "RXOTRX-59-9                        EK0322          LOCAL MODE"
p str.unpack("A20A4A11A16A15" ) # => ["RXOTRX-59-9", "", "", "EK0322", "LOCAL MODE"]
4 голосов
/ 23 марта 2019

Попробуйте, если это может быть жизнеспособным для вас, учитывая data_string:

data_string = "MO                SCGR  SC         RSITE           ALARM_SITUATION\nRXOTG-59            59  0          EK0322          ABIS PATH FAULT\nRXOCF-59                           EK0322          LOCAL MODE\nRXOTRX-59-0         4              EK0322          LOCAL MODE\nRXOTRX-59-1                        EK0322          LOCAL MODE\nRXOTRX-59-4             0          EK0322          LOCAL MODE\nRXOTRX-59-5         1   3          EK0322          LOCAL MODE\nRXOTRX-59-8                        EK0322          LOCAL MODE\nRXOTRX-59-9                        EK0322          LOCAL MODE"

Установите начальную точку каждой строки, так как она кажется выровненной по заголовку.

data = data_string.split("\n")
starts = [0, 18, 24, 35, 51, (data.map(&:size)).max ]

Затем отобразите каждую строку с учетом начальных точек, зачистив пробелы:

data = data.map { |line| starts.each_cons(2).map { |a,b| line[a..b-1].strip } }

Итак, вы получите этот массив:

# [["MO", "SCGR", "SC", "RSITE", "ALARM_SITUATION"]
#  ["RXOTG-59", "59", "0", "EK0322", "ABIS PATH FAULT"]
#  ["RXOCF-59", "", "", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-0", "4", "", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-1", "", "", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-4", "", "0", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-5", "1", "3", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-8", "", "", "EK0322", "LOCAL MODE"]
#  ["RXOTRX-59-9", "", "", "EK0322", "LOCAL MODE"]]

Затем вы можете преобразовать его в хеш или использовать библиотеку csv для управления вашими данными.


Вот способ создать массив хэшей:
headers = data[0]
body = data[1..]

body.map { |line| headers.map(&:to_sym).zip(line).to_h }
#=> [{:MO=>"RXOTG-59", :SCGR=>"59", :SC=>"0", :RSITE=>"EK0322", :ALARM_SITUATION=>"ABIS PATH FAULT"}, {:MO=>"RXOCF-59", :SCGR=>"", :SC=>"", :RSITE=>"EK0322", :ALARM_SITUATION=>"LOCAL MODE"},  ...
1 голос
/ 24 марта 2019

Ваша строка 1 , слегка измененная:

data = <<END
MO                SCGR  SC         RSITE           ALARM_SITUATION
RXOTG-59            59  0          EK0322          ABIS PATH FAULT
RXOCF-59                           EK0322          LOCAL MODE
RXOTRX-59-0         4              EK0322          LOCAL MODE
RXOTRX-59-1                        EK0322          LOCAL MODE
RXOTRX-59-4             0
RXOTRX-59-5         1   3          EK0322          LOCAL MODE
RXOTRX-59-8                        EK0322          LOCAL MODE
RXOTRX-59-9                        EK0322          LOCAL MODE
END

Эта строка очень похожа на структуру данных CSV, поэтому у нас может возникнуть соблазн преобразовать ее в строку CSV, что позволитМы должны использовать методы, предоставляемые CSV классом.

Преобразовать строку в строку CSV

Код

def convert_to_csv(data)
  cols = data[/.+?\n/].gsub(/ \S/).map { |s| Regexp.last_match.begin(0) }
  data.each_line.map do |s|
    cols.each { |i| s[i] = ',' if s.size > i+1 }
    s.gsub(/ *, */, ',')
  end.join
end

Преобразование строки

Теперь преобразуйте строку data в строку CSV.

csv_data = convert_to_csv(data)

puts csv_data
MO,SCGR,SC,RSITE,ALARM_SITUATION
RXOTG-59,59,0,EK0322,ABIS PATH FAULT
RXOCF-59,,,EK0322,LOCAL MODE
RXOTRX-59-0,4,,EK0322,LOCAL MODE
RXOTRX-59-1,,,EK0322,LOCAL MODE
RXOTRX-59-4,,0
RXOTRX-59-5,1,3,EK0322,LOCAL MODE
RXOTRX-59-8,,,EK0322,LOCAL MODE
RXOTRX-59-9,,,EK0322,LOCAL MODE

Объяснение

Шаги выполняются следующим образом.

s = data[/.+?\n/]
  #=> "MO                SCGR  SC         RSITE           ALARM_SITUATION\n" 
e0 = s.gsub(/ \S/)
  #=> #<Enumerator: "MO ... ALARM_SITUATION\n":gsub(/ \S/)>
cols = e0.map { Regexp.last_match.begin(0) - 1 }
  #=> [17, 23, 34, 50] 
e1 = data.each_line
  #=> #<Enumerator: "MO ... LOCAL MODE\n":each_line> 
a = e1.map do |s|
  cols.each { |i| s[i] = ',' if s.size > i+1 }
  s.gsub(/ *, */,',')
end
  #=> ["MO,SCGR,SC,RSITE,ALARM_SITUATION\n",
  #    "RXOTG-59,59,0,EK0322,ABIS PATH FAULT\n",
  #    ...
  #    "RXOTRX-59-9,,,EK0322,LOCAL MODE\n"] 
a.join
  #=> < return value above >

Давайте подробнее рассмотрим вычисление a.Сначала переменная блока s назначается первому элементу, сгенерированному перечислителем e1:

s = e1.next
  #=> "MO                SCGR  SC         RSITE           ALARM_SITUATION\n"

Затем выполняется вычисление блока:

cols.each { |i| s[i] = ',' }
s #=> "MO               ,SCGR ,SC        ,RSITE          ,ALARM_SITUATION\n"
s.gsub(/ *, */,',')
  #=> "MO,SCGR,SC,RSITE,ALARM_SITUATION\n"

Регулярное выражениеиспользуется с gsub читает, «соответствует нулю или больше пробелов, за которыми следует запятая, а затем ноль или больше пробелов».

Когда короткая строка передается в блок, выполняется следующий расчет.

s = "RXOTRX-59-4             0"
s.size
  #=> 25
cols
  #=> [17, 23, 34, 50] 
cols.each { |i| s[i] = ',' if s.size > i+1 }
s #=> "RXOTRX-59-4      ,     ,0" 
s.gsub(/ *, */,',')
  #=> "RXOTRX-59-4,,0" 

Остальные элементы e1 обрабатываются аналогично.

Преобразование строки CSV в хэш

Теперь мы можем использовать методы CSV,Например, предположим, что мы хотим создать массив хэшей, ключи которого являются элементами заголовка, в нижнем регистре и преобразованы в символы, а значения "SCGR" и "SC" должны быть преобразованы в целые числа.Для этого мы используем метод класса CSV :: new , указав соответствующие значения для параметров метода.

Построим хэш

require 'csv'

CSV.new(csv_data, headers: true, header_converters: :symbol,
  converters: :all).to_a.map(&:to_h)
  #=> [{:mo=>"RXOTG-59",    :scgr=>59,  :sc=>0,   :rsite=>"EK0322",
  #     :alarm_situation=>"ABIS PATH FAULT"},
  #    {:mo=>"RXOCF-59",    :scgr=>nil, :sc=>nil, :rsite=>"EK0322",
  #     :alarm_situation=>"LOCAL MODE"},
  #    {:mo=>"RXOTRX-59-0", :scgr=>4,   :sc=>nil, :rsite=>"EK0322",
  #     :alarm_situation=>"LOCAL MODE"},
  #    {:mo=>"RXOTRX-59-1", :scgr=>nil, :sc=>nil, :rsite=>"EK0322",
  #     :alarm_situation=>"LOCAL MODE"},
  #    {:mo=>"RXOTRX-59-4", :scgr=>nil, :sc=>0,   :rsite=>nil,
  #     :alarm_situation=>nil},
  #    {:mo=>"RXOTRX-59-5", :scgr=>1,   :sc=>3,   :rsite=>nil"EK0322",
  #     :alarm_situation=>"LOCAL MODE"},
  #    {:mo=>"RXOTRX-59-8", :scgr=>nil, :sc=>nil, :rsite=>"EK0322",
  #     :alarm_situation=>"LOCAL MODE"},
  #    {:mo=>"RXOTRX-59-9", :scgr=>nil, :sc=>nil, :rsite=>"EK0322",
  #     :alarm_situation=>"LOCAL MODE"}]

Пояснение

Шаги следующие.

csv = CSV.new(csv_data, headers: true, header_converters: :symbol,
  converters: :all)
  #=> <#CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:",
  #         " row_sep:"\n" quote_char:"\"" headers:true> 
a = csv.to_a
  #=> [#<CSV::Row mo:"RXOTG-59" scgr:59 sc:0 rsite:"EK0322" alarm_situation:"ABIS PATH FAULT">,
  #    #<CSV::Row mo:"RXOCF-59" scgr:nil sc:nil rsite:"EK0322" alarm_situation:"LOCAL MODE">,
  #    ...
  #    #<CSV::Row mo:"RXOTRX-59-9" scgr:nil sc:nil rsite:"EK0322" alarm_situation:"LOCAL MODE">] 
a.map(&:to_h)
  #=> < hash shown above >

1 Для запуска кода вам нужно будет сделать отступ в этом heredoc (или изменитьпервая строка data = <<-END.lines.map(&:lstrip).join).

...