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