Quelle est la meilleure façon (la plus efficace) d'analyser un fichier délimité par des tabulations dans Ruby?
La bibliothèque Ruby CSV vous permet de spécifier le délimiteur de champ. Ruby 1.9 utilise FasterCSV . Quelque chose comme ça pourrait fonctionner:
require "csv"
parsed_file = CSV.read("path-to-file.csv", { :col_sep => "\t" })
Les règles pour TSV sont en fait un peu différentes de CSV. La principale différence est que CSV a des dispositions pour coller une virgule à l'intérieur d'un champ, puis utiliser des guillemets et échapper des guillemets à l'intérieur d'un champ. J'ai écrit un exemple rapide pour montrer comment la réponse simple échoue:
require 'csv'
line = 'boogie\ttime\tis "now"'
begin
line = CSV.parse_line(line, col_sep: "\t")
puts "parsed correctly"
rescue CSV::MalformedCSVError
puts "failed to parse line"
end
begin
line = CSV.parse_line(line, col_sep: "\t", quote_char: "Ƃ")
puts "parsed correctly with random quote char"
rescue CSV::MalformedCSVError
puts "failed to parse line with random quote char"
end
#Output:
# failed to parse line
# parsed correctly with random quote char
Si vous souhaitez utiliser la bibliothèque CSV, vous pouvez utiliser un caractère de citation aléatoire que vous ne vous attendez pas à voir si votre fichier (l'exemple le montre), mais vous pouvez également utiliser une méthodologie plus simple comme la classe StrictTsv illustrée ci-dessous pour obtenir le même effet sans avoir à se soucier des devis sur le terrain.
# The main parse method is mostly borrowed from a Tweet by @JEG2
class StrictTsv
attr_reader :filepath
def initialize(filepath)
@filepath = filepath
end
def parse
open(filepath) do |f|
headers = f.gets.strip.split("\t")
f.each do |line|
fields = Hash[headers.Zip(line.split("\t"))]
yield fields
end
end
end
end
# Example Usage
tsv = Vendor::StrictTsv.new("your_file.tsv")
tsv.parse do |row|
puts row['named field']
end
Le choix d'utiliser la bibliothèque CSV ou quelque chose de plus strict dépend simplement de la personne qui vous envoie le fichier et si elle s'attend à adhérer à la norme TSV stricte.
Des détails sur la norme TSV peuvent être trouvés sur http://en.wikipedia.org/wiki/Tab-separated_values
Il existe en fait deux types de fichiers TSV différents.
Fichiers TSV qui sont en fait des fichiers CSV avec un délimiteur défini sur Tab. C'est quelque chose que vous obtiendrez lorsque vous p. Ex. enregistrer une feuille de calcul Excel en tant que "Texte Unicode UTF-16". Ces fichiers utilisent des règles de citation CSV, ce qui signifie que les champs peuvent contenir des tabulations et des retours à la ligne, tant qu'ils sont cités, et les guillemets littéraux sont écrits deux fois. La façon la plus simple de tout analyser correctement est d'utiliser la gemme csv
:
use 'csv'
parsed = CSV.read("file.tsv", col_sep: "\t")
Fichiers TSV conformes à la norme IANA . Les tabulations et les sauts de ligne ne sont pas autorisés en tant que valeurs de champ, et il n'y a aucune citation. C'est quelque chose que vous obtiendrez lorsque vous p. Ex. sélectionnez une feuille de calcul Excel entière et collez-la dans un fichier texte (attention: elle sera gâchée si certaines cellules contiennent des tabulations ou des retours à la ligne). Ces fichiers TSV peuvent être facilement analysés ligne par ligne avec une simple line.rstrip.split("\t", -1)
(remarque -1
, Ce qui empêche split
de supprimer les champs de fin vides). Si vous souhaitez utiliser la gemme csv
, définissez simplement quote_char
Sur nil
:
use 'csv'
parsed = CSV.read("file.tsv", col_sep: "\t", quote_char: nil)
J'aime la réponse de mmmries. CEPENDANT, je déteste la façon dont Ruby supprime toutes les valeurs vides de la fin d'une division. Il ne supprime pas non plus la nouvelle ligne à la fin des lignes.
De plus, j'avais un fichier avec des nouvelles lignes potentielles dans un champ. J'ai donc réécrit son "analyse" comme suit:
def parse
open(filepath) do |f|
headers = f.gets.strip.split("\t")
f.each do |line|
myline=line
while myline.scan(/\t/).count != headers.count-1
myline+=f.gets
end
fields = Hash[headers.Zip(myline.chomp.split("\t",headers.count))]
yield fields
end
end
end
Cela concatène toutes les lignes si nécessaire pour obtenir une ligne complète de données et renvoie toujours l'ensemble complet de données (sans aucune entrée potentielle à la fin).