我应该如何检测文本文件中使用了哪个分隔符?

Posted

技术标签:

【中文标题】我应该如何检测文本文件中使用了哪个分隔符?【英文标题】:How should I detect which delimiter is used in a text file? 【发布时间】:2010-10-20 04:51:01 【问题描述】:

我需要能够解析 CSV 和 TSV 文件。我不能依靠用户知道区别,所以我想避免要求用户选择类型。有没有一种简单的方法来检测正在使用的分隔符?

一种方法是阅读每一行并计算制表符和逗号,并找出在每一行中最常用的。当然,数据可以包含逗号或制表符,所以说起来容易做起来难。

编辑:这个项目的另一个有趣的方面是,当我读入文件时,我还需要检测文件的架构,因为它可能是众多文件之一。这意味着在解析之前我不会知道我有多少字段。

【问题讨论】:

【参考方案1】:

在 Python 中,csv 模块中有一个 Sniffer 类,可用于猜测给定文件的分隔符和引号字符。它的策略是(引用自 csv.py 的文档字符串):


[首先,查找] 包含在两个相同引号之间的文本 (可能的引号字符)之前和之后 由相同的字符(可能的分隔符)。 例如:

         ,'some text',

获胜次数最多的引号,与分隔符相同。 如果没有quotechar,则无法确定分隔符 这边。

在这种情况下,请尝试以下操作:

分隔符应该在 每一行。但是,由于数据格式不正确,它可能不会。我们不想 一种全有或全无的方法,因此我们允许在此进行微小的变化 号码。

    建立频率表 每一行的每个字符。 建立一个频率表 频率(元频率?),例如 'x 在 10 行中出现 5 次,6 1000 行次,2 次 7 次 行' 使用元频率的模式 确定预期 该字符的频率 找出角色的频率 确实达到了这个目标 最符合它的角色 目标是分隔符

出于性能原因,数据以块的形式进行评估,因此它可以 尝试评估数据的最小部分,评估 根据需要添加额外的块。


我不会在这里引用源代码 - 它位于每个 Python 安装的 Lib 目录中。

请记住,CSV 也可以使用分号而不是逗号作为分隔符(例如,在德语版本的 Excel 中,CSV 是用分号分隔的,因为在德国使用逗号作为小数分隔符...)

【讨论】:

【参考方案2】:

您可以在预览窗口中向他们展示结果 - 类似于 Excel 的做法。在这种情况下使用错误的分隔符时非常清楚。然后,您可以允许他们选择一系列分隔符并实时更新预览。

然后您可以简单地猜测一下分隔符的开头(例如,逗号或制表符是否在前)。

【讨论】:

我认为在导入前向用户显示结果是一个很好的举措,但明智地猜测对用户体验也很有好处。所以这个组合真的很好! 一个建议——如果你正在做一个预览窗口并且你想“猜测”哪个是正确的分隔符,那么你可以分割一个可能的分隔符。并查看前十行是否都具有相同数量的字段,与所有其他正常分隔符进行比较。一个不错的选择是始终使用相同数量的字段。作为Jon Skeet said,它完全有可能是一个有效的逗号分隔和制表符分隔,但该制表符是预期的选择。【参考方案3】:

我遇到了类似的需求,并认为我会分享我的想法。我还没有通过它运行大量数据,因此可能存在边缘情况。另外,请记住,此函数的目标不是 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】:

你知道每行应该有多少个字段吗?如果是这样,我会阅读文件的前几行并据此进行检查。

根据我的经验,“普通”数据通常包含逗号,但很少包含制表符。这表明您应该检查前几行中的选项卡数量是否一致,并将该选择作为首选猜测。当然,这取决于您所获得的数据。

最终,很有可能拥有一个对两种格式都完全有效的文件——所以你不能让它绝对万无一失。这必须是一项“尽力而为”的工作。

【讨论】:

【参考方案5】:

它是用 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 中的每一行,并检查每个分隔符出现的次数是否恒定。如果不是,则删除候选分隔符,最终您应该得到一个分隔符。

【讨论】:

【参考方案6】:

我想您建议的解决方案将是最好的方法。在格式良好的 CSV 或 TSV 文件中,每行逗号或制表符的数量应该是恒定的(根本没有变化)。对文件的每一行进行计数,并检查哪一个对于所有行都是恒定的。每行的两个分隔符的计数似乎不太可能相同,但在这种不可思议的罕见情况下,您当然可以提示用户。

如果制表符和逗号的数量都不是恒定的,则向用户显示一条消息,告诉他们文件格式错误,但程序认为它是一个(任何格式的每行分隔符标准偏差最低的)文件。

【讨论】:

【参考方案7】:

只需阅读几行,数数逗号数和制表符数并进行比较。如果有 20 个逗号且没有制表符,则为 CSV。如果有 20 个制表符和 2 个逗号(可能在数据中),则它在 TSV 中。

【讨论】:

【参考方案8】:

没有“高效”的方法。

【讨论】:

【参考方案9】:

假设每行有固定数量的字段,并且值中的任何逗号或制表符都用引号 (") 括起来,您应该能够计算出每行中每个字符的频率。如果字段不是固定的,这更难,而且如果不使用引号将其他分隔字符括起来,我怀疑这几乎是不可能的(并且取决于数据,特定于区域设置)。

【讨论】:

【参考方案10】:

根据我的经验,数据很少包含制表符,因此一行制表符分隔的字段(通常)会相当明显。

不过,逗号更难使用 - 尤其是当您在非美国语言环境中读取数据时。如果您正在阅读在国外生成的文件,数字数据可能包含大量逗号,因为浮点数通常会包含它们。

最后,唯一安全的做法通常是尝试,然后将其呈现给用户并允许他们进行调整,尤其是当您的数据包含逗号和/或制表符时。

【讨论】:

【参考方案11】:

我认为在普通文本中,制表符非常少见,除了作为一行中的第一个字符——想想缩进的段落或源代码。我认为,如果您发现嵌入的制表符(即不遵循逗号的制表符),您可以假设这些制表符被用作分隔符并且大部分时间都是正确的。这只是一种预感,没有经过任何研究证实。我当然会为用户提供覆盖自动计算模式的选项。

【讨论】:

【参考方案12】:

假设您有一组您期望的标准列...

我会使用 FileHelper(SourceForge 上的开源项目)。 http://filehelpers.sourceforge.net/

定义两个阅读器模板,一个用于逗号,一个用于制表符。

如果第一个失败,请尝试第二个。

【讨论】:

这很有趣。我将阅读多个模式并尝试根据文件布局(字段数、字段顺序等)找出当前文件是哪个模式。 FileHelper 能否确定在运行时使用哪个类?【参考方案13】:

您可以像这样检查一行是否使用一个分隔符或另一个:

while ((line = readFile.ReadLine()) != null)

    if (line.Split('\t').Length > line.Split(',').Length) // tab delimited or comma delimited?
        row = line.Split('\t');
    else
        row = line.Split(',');

    parsedData.Add(row);

【讨论】:

如果它是制表符分隔的数据中的一堆逗号,反之亦然?这可能会尝试根据行中的数据以制表符分隔或逗号分隔格式解析同一文件。

以上是关于我应该如何检测文本文件中使用了哪个分隔符?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 IFS 下载文本文件的最后一行删除 CRLF

如何通过特定的行分隔符读取文本文件?

如何检测文本文件中第二次出现空行?

使用带有自定义分隔符的 postgres 按字符大小复制文本文件

如何在颤动中创建带有文本和分隔符的详细信息框

C++ 从文本文件检查操作系统