8

我需要能够确定在我的 Ruby 项目中的 csv 文件(逗号、空格或分号)中使用了哪个分隔符。我知道,在 Python 中的 csv 模块中有一个 Sniffer 类,可用于猜测给定文件的分隔符。Ruby中是否有类似的东西?非常感谢任何形式的帮助或想法。

4

3 回答 3

13

看起来 py 实现只检查一些方言:excel 或 excel_tab。所以,一个简单的实现只是检查","or"\t"是:

COMMON_DELIMITERS = ['","',"\"\t\""].freeze

def sniff(path)
  first_line = File.open(path).first
  return unless first_line

  snif = {}
  COMMON_DELIMITERS.each do |delim| 
    snif[delim] = first_line.count(delim)
  end
  snif = snif.sort { |a,b| b[1]<=>a[1] }

  snif[0][0] if snif.size > 0
end

注意:这将返回它找到的完整分隔符,例如",",因此,您可以snif[0][0]snif[0][0][1].

另外,我正在使用count(delim)它是因为它更快一些,但是如果您添加了一个由两个(或更多)相同类型的字符组成的分隔符--,那么在权衡类型时它可能会每次出现两次(或更多) ,所以在这种情况下,最好使用scan(delim).length.

于 2013-02-04T20:47:23.290 回答
12

这是Gary S. Weaver的回答,因为我们在生产中使用它。很好的解决方案,效果很好。

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

规格

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
于 2015-10-08T09:17:40.850 回答
2

我不知道 Ruby 1.9 中包含的 CSV 库中有任何嗅探器实现。它将尝试自动发现行分隔符,但默认情况下假定列分隔符为逗号。

一个想法是尝试使用每个可能的分隔符解析样本行数(可能是总数的 5%?)。无论哪个分隔符最一致地导致相同的列数,都可能是正确的分隔符。

于 2013-02-04T20:18:35.533 回答