Scala: автоматическое определение разделителя / разделителя в CSV-файле


Я использую библиотеку OpenCSV для разделения моих CSV-файлов. Теперь мне нужно определить символ разделителя / разделителя с абсолютной уверенностью. Я искал в сети, но нашел только примеры, когда вы создаете список кандидатов и пробуете один из них. Я не думаю, что это лучший способ, потому что вы, скорее всего, получите ошибки. Мой разделитель должен правильно работать на любом CSV (который я не контролирую), поэтому он должен быть максимально универсальным. Есть ли у кого-нибудь хорошее решение?

2 2

2 ответа:

Возможно, вы уже видели этот вопрос , связанный с SO, в котором перечислены хорошие стратегии, такие как подсчет количества раз, когда появляется потенциальный разделитель, и/или проверка того, что каждая строка имеет одинаковое количество столбцов при использовании гипотетического разделителя.

К сожалению, абсолютная определенность невозможна, поскольку формат не содержит способа однозначного указания разделителя внутри файла. Я думаю, что лучшим решением для того, чтобы сделать его как можно более универсальным, было бы: заставьте пользователя указать, когда это не запятая (как это делает opencsv), или, возможно, разрешите клиенту указать разделитель, если вы или они решите, что автоматическое обнаружение не удалось. Если это не может быть интерактивным, то я думаю, что лучшее, что вы можете сделать, это зарегистрировать случаи, когда вы считаете, что это не удалось, чтобы они могли справиться с этим позже.

Кроме того, я думаю, что частота ошибок будет ниже, чем вы ожидаете. Я предполагаю, что в 99% случаев разделителем будет запятая, точка с запятой, точка с запятой или табуляция. Я, к сожалению, видел, как ленивые кодеры используют что-то вроде каретки, трубы или Тильды для разграничения полей в предположении, что данные не будут содержать его, поэтому им не придется делать правильный экранирование. Но это не является нормой, и это не должно рассматриваться как CSV.

Модуль Python csv имеет классSniffer , который угадывает разделители (пользователь предоставляет список кандидатов); вы можете посмотреть на его реализацию .

Я недавно играл с проблемой обнаружения разделителя / разделителя CSV-файлов. Я придумал следующее, что, как я надеюсь, поможет другим и, возможно, получит обратную связь, чтобы улучшить.

Мое решение основано на нескольких статьях, которые я прочитал по этой проблеме. Поскольку нет никаких ограничений на то, каким может быть разделитель полей, я решил использовать таблицу ASCII и исключить очевидные (буквенно-цифровые символы) и не столь очевидные (непечатаемые), за исключением Код вкладки. Используя эти значения, я заполнил словарь кодом 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

Это работает для меня, но я всегда рад, что мне показывают лучший, более оптимизированный способ.