2
votes

Comment analyser une chaîne dans une table de hachage dans Ruby

J'obtiens des données sous forme de chaîne à partir d'un appareil distant. J'ai besoin d'analyser les données. Les données se présentent généralement comme suit:

   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

J'adorerai avoir les données sous forme de tableau de tableaux ou de toute autre structure sensible par programme.

Je divise les données en un tableau utilisant:

["RXOTRX-59-9", "EK0322", "LOCAL MODE"]

puis en supprimant l'espace supplémentaire sur chaque élément du tableau avec:

RXOTRX-59-9                        EK0322          LOCAL MODE

mais ceci a une limitation en ce que les cellules vides ne sont pas prises en compte. Je m'attends à ce que le tableau contienne cinq éléments, mais à la place il en contient moins de cinq.

Cas 1: Dans ce cas, j'obtiens le résultat attendu:

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

se convertit en

RXOTG-59            59  0          EK0322          ABIS PATH FAULT

Cas 2: Dans ce cas, j'obtiens un résultat inattendu:

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

se convertit en

str.split("\r\n")
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


0 commentaires

3 Réponses :


3
votes

Essayez si cela peut être viable pour vous, étant donné le data_string :

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"},  ...

Définissez le point de départ de chaque ligne, car il semble être aligné avec le en-tête.

# [["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"]]

Ensuite, mappez chaque ligne en considérant les points de départ, en supprimant les espaces de fin:

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

Vous allez donc finir avec ce tableau:

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

Vous pouvez ensuite le convertir en hachage ou utiliser la bibliothèque csv pour manipuler vos données.

Voici un moyen de générer un tableau de hachages:

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"


2 commentaires

Ce serait mieux si vous pouvez obtenir les index dans démarre automatiquement en utilisant les informations d'en-tête. Vous voyez que chaque nom de colonne n'inclut pas d'espace? De plus, le code compliqué pour obtenir le dernier index n'est pas nécessaire. Vous pouvez y avoir 0 .


@sawa, merci d'avoir indiqué que 0 fonctionne quand même comme dernier point de départ. Je n'ai pas ajouté d'automatisation pour trouver les points de départ car n'étant pas à l'aise avec les expressions régulières, j'ai trouvé un moche: data [0] .each_char.each_cons (2) .with_index.with_object ([]) {| ((a , b), i), res | res << i + 1 if (a == '' && b! = '')} . Alors, j'ai décidé de passer.



4
votes

String.unpack avec directive " Un "est bien pour les chaînes de largeur fixe.

str = "RXOTRX-59-9                        EK0322          LOCAL MODE"
p str.unpack("A20A4A11A16A15" ) # => ["RXOTRX-59-9", "", "", "EK0322", "LOCAL MODE"]


1 commentaires

C'est bon à savoir, très utile. J'ai remarqué que si la longueur de la chaîne est inférieure à 20 + 4 + 11 + 16 + 15 , unpack remplit efficacement la chaîne avec des espaces pour en faire la longueur indiquée avant exécuter la directive (une commodité).



0
votes

Votre chaîne 1 , légèrement modifiée:

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 >

Cette chaîne ressemble beaucoup à une structure de données CSV, nous pourrions donc être tentés de la convertir en chaîne CSV , nous permettant ainsi de mettre en œuvre les méthodes fournies par le Classe CSV .

Convertir la chaîne en chaîne CSV

Code

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"}]

Convertir la chaîne

Convertissez maintenant la chaîne data en chaîne CSV.

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" 


6 commentaires

Notez que @iGian avait la même idée de convertir la chaîne en chaîne CSV dans sa réponse précédente.


J'ai remarqué que la méthode convert_to_csv n'a pas d'instruction de retour, y a-t-il une raison à cela?


On pourrait écrire return data.each_line.map do | s | ... end.join , mais le mot-clé return est redondant car data.each_line.map do | s | ... end.join est la dernière expression évaluée dans la méthode. Sa valeur de retour est donc la valeur de retour de la méthode.


Salut, j'ai remarqué une dernière chose, lorsque le champ ALARM_SITUATION est vide, je veux dire que sans même un espace vide, il ne fonctionne pas, vérifiez ce lien, rextester.com/YAB84581


Emmanuel, merci pour le heads-up. J'ai corrigé cela et modifié légèrement l'exemple pour illustrer le problème.


Il y avait une proposition de modification qui avait été rejetée par les modérateurs avant que je ne l'ai vue. Je n'ai pas remarqué qui l'a posté et il semble maintenant avoir disparu. Je tiens à remercier celui qui l'a posté, car je pense qu'il était lié à un problème qu'Emmanuel a noté dans un commentaire ci-dessus (que j'ai corrigé par la suite). Je pense qu'il est généralement préférable, cependant, d'informer l'auteur d'un problème dans un commentaire, suggérant éventuellement une solution, mais laissez l'auteur effectuer la modification nécessaire.