Как мне определить, какой разделитель используется в текстовом файле? - PullRequest
20 голосов
/ 17 апреля 2009

Мне нужно иметь возможность анализировать файлы CSV и TSV. Я не могу полагаться на то, что пользователи будут знать разницу, поэтому я хотел бы не просить пользователя выбрать тип. Есть ли простой способ определить, какой разделитель используется?

Один из способов - прочитать каждую строку, сосчитать табуляцию и запятую и выяснить, какой из них наиболее последовательно используется в каждой строке. Конечно, данные могут включать запятые или табуляции, так что это легче сказать, чем сделать.

Редактировать: Еще один интересный аспект этого проекта заключается в том, что мне также понадобится определить схему файла при его чтении, поскольку он может быть одним из многих. Это значит, что я не буду знать, сколько у меня полей, пока не смогу их проанализировать.

Ответы [ 13 ]

15 голосов
/ 18 апреля 2009

В Python в модуле csv есть класс Sniffer, который можно использовать для угадывания данного разделителя файла и символов кавычек. Его стратегия (цитируется из строки документации csv.py):


[Сначала посмотрите] текст, заключенный между двумя одинаковыми кавычками (вероятный цитата), которые предшествуют и следуют одним и тем же символом (вероятный разделитель). Например:

         ,'some text',

Цитата с наибольшим количеством выигрышей, также как и с разделителем. Если кавычка нет, разделитель не может быть определен вот так.

В этом случае попробуйте следующее:

Разделитель должен встречаться столько же раз каждый ряд. Однако из-за искаженных данных это может и не произойти. Мы не хотим подход «все или ничего», поэтому мы допускаем небольшие изменения в этом число.

  1. построить таблицу частоты каждый символ в каждой строке.
  2. построить таблицу частот этого частота (мета-частота?), например х произошел 5 раз в 10 строках, 6 раз в 1000 строк, 7 раз в 2 строки
  3. использовать режим мета-частоты определить ожидаемый частота для этого символа
  4. узнать, как часто персонаж на самом деле отвечает этой цели
  5. персонаж, который лучше всего соответствует его цель - разделитель

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


Я не собираюсь здесь цитировать исходный код - он находится в каталоге Lib каждой установки Python.

Помните, что CSV также может использовать точки с запятой вместо запятых в качестве разделителей (например, в немецких версиях Excel, CSV разделены точкой с запятой, поскольку запятые используются в качестве десятичных разделителей в Германии ...)

14 голосов
/ 17 апреля 2009

Вы можете показать им результаты в окне предварительного просмотра - подобно тому, как это делает Excel. Довольно ясно, когда в этом случае используется неправильный разделитель. Затем вы можете позволить им выбрать диапазон разделителей и получить предварительный просмотр в реальном времени.

Тогда вы могли бы просто сделать простое предположение относительно разделителя, с которого нужно начать (например, стоит ли ставить запятую или табуляцию).

4 голосов
/ 04 февраля 2015

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

/// <summary>
/// Analyze the given lines of text and try to determine the correct delimiter used. If multiple
/// candidate delimiters are found, the highest frequency delimiter will be returned.
/// </summary>
/// <example>
/// string discoveredDelimiter = DetectDelimiter(dataLines, new char[] { '\t', '|', ',', ':', ';' });
/// </example>
/// <param name="lines">Lines to inspect</param>
/// <param name="delimiters">Delimiters to search for</param>
/// <returns>The most probable delimiter by usage, or null if none found.</returns>
public string DetectDelimiter(IEnumerable<string> lines, IEnumerable<char> delimiters) {
  Dictionary<char, int> delimFrequency = new Dictionary<char, int>();

  // Setup our frequency tracker for given delimiters
  delimiters.ToList().ForEach(curDelim => 
    delimFrequency.Add(curDelim, 0)
  );

  // Get a total sum of all occurrences of each delimiter in the given lines
  delimFrequency.ToList().ForEach(curDelim => 
    delimFrequency[curDelim.Key] = lines.Sum(line => line.Count(p => p == curDelim.Key))
  );

  // Find delimiters that have a frequency evenly divisible by the number of lines
  // (correct & consistent usage) and order them by largest frequency
  var possibleDelimiters = delimFrequency
                    .Where(f => f.Value > 0 && f.Value % lines.Count() == 0)
                    .OrderByDescending(f => f.Value)
                    .ToList();

  // If more than one possible delimiter found, return the most used one
  if (possibleDelimiters.Any()) {
    return possibleDelimiters.First().Key.ToString();
  }
  else {
    return null;
  }   

}
4 голосов
/ 17 апреля 2009

Знаете ли вы, сколько полей должно присутствовать в каждой строке? Если это так, я бы прочитал первые несколько строк файла и проверил на основе этого.

По моему опыту, "нормальные" данные довольно часто содержат запятые, но редко содержат символы табуляции. Это говорит о том, что вам следует проверить постоянство количества вкладок в первых нескольких строках и выбрать этот вариант в качестве предпочтительного варианта. Конечно, это зависит от того, какие именно данные у вас есть.

В конечном итоге, вполне возможно иметь файл, который полностью подходит для обоих форматов, поэтому вы не можете сделать его абсолютно надежным. Это должна быть работа "с максимальным усилием".

3 голосов
/ 24 августа 2010

Это на PHP, но это выглядит довольно надежно:

$csv = 'something;something;something
someotherthing;someotherthing;someotherthing
';
$candidates = array(',', ';', "\t");
$csvlines = explode("\n", $csv);
foreach ($candidates as $candidatekey => $candidate) {
 $lastcnt = 0;
 foreach ($csvlines as $csvline) {
  if (strlen($csvline) <= 2) continue;
  $thiscnt = substr_count($csvline, $candidate);
  if (($thiscnt == 0) || ($thiscnt != $lastcnt) && ($lastcnt != 0)) {
   unset($candidates[$candidatekey]);
   break;
  }
  $lastcnt = $thiscnt;
 }
}
$delim = array_shift($candidates);
echo $delim;

Что делает следующее: Для каждого указанного возможного разделителя он считывает каждую строку в CSV и проверяет, является ли число раз, когда каждый разделитель является постоянным. Если нет, то кандидат-разделитель удаляется, и в конечном итоге вы должны получить один разделитель.

2 голосов
/ 18 апреля 2009

Просто прочитайте несколько строк, посчитайте количество запятых и количество вкладок и сравните их. Если есть 20 запятых и нет вкладок, это в CSV. Если есть 20 вкладок и 2 запятые (возможно, в данных), это в TSV.

2 голосов
/ 17 апреля 2009

Я полагаю, что предложенное вами решение будет лучшим вариантом. В правильно сформированном файле CSV или TSV количество запятых или табуляций соответственно на строку должно быть постоянным (без изменений). Сделайте подсчет каждого для каждой строки файла и проверьте, какая из них постоянна для всех строк. Может показаться маловероятным, что счетчики обоих разделителей для каждой строки идентичны, но в этом невероятно редком случае вы, конечно, могли бы подсказать пользователю.

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

1 голос
/ 18 апреля 2009

Предполагая, что у вас есть стандартный набор столбцов, которые вы ожидаете ...

Я бы использовал FileHelper (проект с открытым исходным кодом на SourceForge). http://filehelpers.sourceforge.net/

Определите два шаблона считывателя, один для комы, один для вкладок.

Если первый сбой, попробуйте второй.

1 голос
/ 18 апреля 2009

Я бы предположил, что в обычном тексте вкладки встречаются очень редко, за исключением первого символа (символов) в строке - например, абзацы с отступом или исходный код. Я думаю, что если вы найдете встроенные вкладки (то есть те, которые не следуют за запятыми), вы можете предположить, что вкладки используются в качестве разделителей и быть правильными большую часть времени. Это всего лишь догадка, не подтвержденная никакими исследованиями. Конечно, я бы дал пользователю возможность переопределить режим автоматического расчета.

1 голос
/ 17 апреля 2009

По моему опыту, данные редко содержат вкладки, поэтому строка полей с разделителями вкладок (как правило) будет довольно очевидной.

Хотя запятые сложнее, особенно если вы читаете данные в неамериканских регионах. Числовые данные могут содержать огромное количество запятых, если вы читаете файлы, созданные за пределами страны, поскольку числа с плавающей запятой часто содержат их.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...