Ich muss herausfinden, welches Trennzeichen in einer CSV-Datei (Komma, Leerzeichen oder Semikolon) in meinem Ruby-Projekt verwendet wird. Ich weiß, es gibt eine Sniffer-Klasse in Python im csv-Modul, mit der man den Begrenzer einer bestimmten Datei erraten kann. Gibt es etwas Ähnliches in Ruby? Jede Art von Hilfe oder Idee wird sehr geschätzt.Ruby: Wie kann ich das in einer CSV-Datei verwendete Trennzeichen erkennen/intelligent erraten?
Antwort
Sieht aus wie die Py-Implementierung überprüft nur ein paar Dialekte: Excel oder Excel_Tab. So ist eine einfache Implementierung von etwas, das nur überprüft für ","
oder "\t"
ist:
COMMON_DELIMITERS = ['","',"\"\t\""]
def sniff(path)
first_line = File.open(path).first
return nil unless first_line
snif = {}
COMMON_DELIMITERS.each {|delim|snif[delim]=first_line.count(delim)}
snif = snif.sort {|a,b| b[1]<=>a[1]}
snif.size > 0 ? snif[0][0] : nil
end
Hinweis: das wäre den vollständigen Begrenzer zurückkehrt es findet, zum Beispiel ","
, um ,
zu erhalten, könnten Sie die snif[0][0]
zu snif[0][0][1]
ändern.
Auch ich benutze count(delim)
, weil es ein wenig schneller ist, aber wenn Sie ein Trennzeichen, das aus zwei (oder mehr) Zeichen des gleichen Typs wie --
zusammengesetzt ist, dann könnte es jedes Auftreten zweimal (oder mehr) beim Wiegen des Typs, so dass es in diesem Fall besser sein kann, scan(delim).length
zu verwenden.
Mir ist keine Sniffer-Implementierung in der in Ruby 1.9 enthaltenen CSV-Bibliothek bekannt. Es wird versucht, das Zeilentrennzeichen automatisch zu erkennen, aber das Spaltentrennzeichen wird standardmäßig als Komma angenommen.
Eine Idee wäre es, versuchen eine Probe Anzahl von Zeilen (5% der Gesamtmenge vielleicht?) Parsing mit jedem der möglichen Trennzeichen. Unabhängig davon, welcher Separator zu der gleichen Anzahl von Spalten führt, ist wahrscheinlich der richtige Separator.
Hier ist Gary S. Weaver Antwort, wie wir es in der Produktion verwenden. Gute Lösung, die gut funktioniert.
class ColSepSniffer
NoColumnSeparatorFound = Class.new(StandardError)
EmptyFile = Class.new(StandardError)
COMMON_DELIMITERS = [
'","',
'"|"',
'";"'
].freeze
def initialize(path)
@path = path
end
def self.find(path)
new(path: path).find
end
def find
fail EmptyFile unless first
if valid?
delimiters[0][0][1]
else
fail NoColumnSeparatorFound
end
end
private
def valid?
!delimiters.collect(&:last).reduce(:+).zero?
end
# delimiters #=> [["\"|\"", 54], ["\",\"", 0], ["\";\"", 0]]
# delimiters[0] #=> ["\";\"", 54]
# delimiters[0][0] #=> "\",\""
# delimiters[0][0][1] #=> ";"
def delimiters
@delimiters ||= COMMON_DELIMITERS.inject({}, &count).sort(&most_found)
end
def most_found
->(a, b) { b[1] <=> a[1] }
end
def count
->(hash, delimiter) { hash[delimiter] = first.count(delimiter); hash }
end
def first
@first ||= file.first
end
def file
@file ||= File.open(@path)
end
end
Spec
require "spec_helper"
describe ColSepSniffer do
describe ".find" do
subject(:find) { described_class.find(path) }
let(:path) { "./spec/fixtures/google/products.csv" }
context "when , delimiter" do
it "returns separator" do
expect(find).to eq(',')
end
end
context "when ; delimiter" do
let(:path) { "./spec/fixtures/google/products_with_semi_colon_seperator.csv" }
it "returns separator" do
expect(find).to eq(';')
end
end
context "when | delimiter" do
let(:path) { "./spec/fixtures/google/products_with_bar_seperator.csv" }
it "returns separator" do
expect(find).to eq('|')
end
end
context "when empty file" do
it "raises error" do
expect(File).to receive(:open) { [] }
expect { find }.to raise_error(described_class::EmptyFile)
end
end
context "when no column separator is found" do
it "raises error" do
expect(File).to receive(:open) { [''] }
expect { find }.to raise_error(described_class::NoColumnSeparatorFound)
end
end
end
end
Technisch nur einer von denen ist eine CSV-Datei ... –