【问题标题】:Scala: Auto detection of delimiter/separator in CSV fileScala:自动检测 CSV 文件中的分隔符/分隔符
【发布时间】:2014-07-08 14:44:42
【问题描述】:

我正在使用 OpenCSV 库来拆分我的 CSV 文件。现在我需要绝对确定地检测定界符/分隔符。 我在网上搜索过,但只找到了创建候选人列表并尝试其中一个的示例。我认为这不是最好的方法,因为你很可能会出错。 我的拆分器应该在任何 CSV(我无法控制)上正常工作,因此它必须尽可能通用。 谁有好的解决办法?

【问题讨论】:

  • grumble mode on 有趣的是 CSV 代表 comma 分隔值。
  • @om-nom-nom 如果总是这样就好了。但现实不同
  • @om-nom-nom:有人说 C 代表字符。

标签: scala csv split separator opencsv


【解决方案1】:

您可能已经看过这个related SO question,其中列出了很好的策略,例如计算潜在分隔符出现的次数,和/或在使用假设分隔符时验证每行具有相同的列数。

不幸的是,绝对确定性是不可能的,因为该格式不包括在文件中明确指定分隔符的方法。我认为使其尽可能通用的最佳解决方案是让用户指定它何时不是逗号(这是 opencsv 处理它的方式),或者如果您或他们确定,可能允许客户端指定分隔符那个自动检测失败了。如果这不能是交互式的,那么我认为您能做的最好的事情就是记录您认为它失败的案例,以便他们以后可以处理。

另外,我认为错误率会低于您的预期。我的猜测是 99% 的时间分隔符将是逗号、分号、句点或制表符。不幸的是,我看到懒惰的编码人员在假设数据不包含字段的情况下使用诸如插入符号、管道或波浪号之类的东西来分隔字段,因此他们不必进行适当的转义。但这不是常态,也不应该被视为 CSV。

Python csv 模块有一个 Sniffer 类猜测分隔符(用户提供候选列表);你可能想看看它的implementation

【讨论】:

    【解决方案2】:

    How to determine the delimiter in CSV file 中,我发现提到了 Univocity-Parsers,它看起来像是一个维护良好且流行的库,实际上提供了一个 API 来为您处理检测。

    【讨论】:

      【解决方案3】:

      我最近一直在玩弄 CSV 文件的分隔符/定界符检测问题。我提出了以下建议,希望能对其他人有所帮助,并可能会收到反馈以进行改进。

      我的解决方案基于我阅读过的有关该问题的几篇文章。 因为对字段分隔符没有限制,所以我决定使用 ASCII 表并消除明显的(字母数字字符)和不那么明显的(不可打印的),但 TAB 代码除外。使用这些值,我填充了一个字典,其中 ASCII 代码是键,值将用我的代码填充。

      然后就是逐行读取 CSV,查看每一行是否出现任何字典键字符,并增加我遇到的每个键字符的值。循环继续到文件末尾或在此示例中限制为 100 次。您可以根据需要更改此设置,但 100 足以检测分隔符。分隔符然后由具有最大值的字典键(ASCII 码)确定。

      调用例程示例

      private sub Main()
          dim separator As Char
          separator= separatorDetect(txtInputFile.Text)
      end sub
      

      主要检测功能

      Private Function separatorDetect(ByVal StrFileName As String) As Char
          Dim i As Int16 = 0
          Dim separator As List(Of Char)
          Dim dictSeparators As New Dictionary(Of Integer, Integer)
          dictSeparators.Add(9, 0)
          dictSeparators.Add(33, 0)
          For i = 35 To 47
              dictSeparators.Add(i, 0)
          Next
          For i = 91 To 96
              dictSeparators.Add(i, 0)
          Next
          For i = 123 To 126
              dictSeparators.Add(i, 0)
          Next
          Dim lineCounter As Integer = 0
          Dim line As String = String.Empty
          Dim keyList As New List(Of Integer)
          For Each key In dictSeparators.Keys
              keyList.Add(key)
          Next
          Dim tmp As Char
          Using textReader = New StreamReader(StrFileName)
              Do Until textReader.EndOfStream
                  line = textReader.ReadLine.Trim
                  For Each key In keyList
                      tmp = Convert.ToChar(key)
                      dictSeparators.Item(key) = dictSeparators.Item(key) + InStrCount(line, tmp)
                  Next
                  lineCounter += 1
                  If lineCounter = 99 Then GoTo readEnd
              Loop
          End Using
      readEnd:
          Dim max = dictSeparators.Aggregate(Function(l, r) If(l.Value > r.Value, l, r)).Key
          Return Chr(max)
      End Function
      

      计数函数的递归索引

      Private Function InStrCount(ByVal SourceString As String, ByVal SearchString As Char, _
                      Optional ByRef StartPos As Integer = 0, _
                      Optional ByRef Count As Integer = 0) As Integer
          If SourceString.IndexOf(SearchString, StartPos) > -1 Then
              Count += 1
              InStrCount(SourceString, SearchString, SourceString.IndexOf(SearchString, StartPos) + 1, Count)
          End If
          Return Count
      End Function
      

      这对我有用,但我总是很高兴看到更好的优化方式。

      【讨论】:

        猜你喜欢
        • 2021-12-17
        • 2018-02-18
        • 2015-08-09
        • 2020-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-12
        相关资源
        最近更新 更多