Tôi cần có khả năng tìm ra dấu phân cách nào đang được sử dụng trong tệp csv (dấu phẩy, dấu cách hoặc dấu chấm phẩy) trong dự án Ruby của tôi. Tôi biết, có một lớp Sniffer trong Python trong mô-đun csv có thể được sử dụng để đoán dấu phân tách của tệp đã cho. Có gì tương tự với Ruby không? Bất kỳ loại trợ giúp hoặc ý tưởng nào được đánh giá cao.Ruby: Làm thế nào tôi có thể phát hiện/đoán thông minh dấu phân cách được sử dụng trong tệp CSV?
Trả lời
Dường như việc triển khai py chỉ kiểm tra một vài phương ngữ: excel hoặc excel_tab. Vì vậy, việc thực hiện đơn giản của một cái gì đó mà chỉ kiểm tra ","
hoặc "\t"
là:
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
Lưu ý: rằng sẽ trả lại đầy đủ delimiter nó tìm thấy, ví dụ ","
, do đó, để có được ,
bạn có thể thay đổi snif[0][0]
thành snif[0][0][1]
.
Ngoài ra, tôi đang sử dụng count(delim)
vì nó nhanh hơn một chút, nhưng nếu bạn thêm dấu phân cách gồm hai (hoặc nhiều hơn) ký tự cùng loại như --
thì có thể mỗi lần xuất hiện hai lần (hoặc nhiều hơn) khi cân nặng loại, vì vậy trong trường hợp đó, nó có thể là tốt hơn để sử dụng scan(delim).length
.
Tôi không biết về bất kỳ triển khai trình thám thính nào trong thư viện CSV có trong Ruby 1.9. Nó sẽ cố gắng tự động phát hiện ra dấu tách hàng, nhưng dấu tách cột được giả định là dấu phẩy theo mặc định.
Một ý tưởng sẽ là thử phân tích một số hàng mẫu (5% tổng số có thể?) Bằng cách sử dụng từng dấu phân cách có thể. Bất kỳ dấu phân tách nào dẫn đến cùng một số cột nhất quán có lẽ là dấu phân cách chính xác.
Đây là câu trả lời Gary S. Weaver khi chúng tôi đang sử dụng nó trong quá trình sản xuất. Giải pháp tốt hoạt động tốt.
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
Về mặt kỹ thuật, chỉ là một trong những là một tập tin CSV ... –