在C#中查找CSV或文本文件的分隔符

4

我希望找到用于分隔csv或文本文件中列的分隔符。

我正在使用TextFieldParser类读取这些文件。

下面是我的代码,

String path = @"c:\abc.csv";
DataTable dt = new DataTable();
if (File.Exists(path))
{
    using (Microsoft.VisualBasic.FileIO.TextFieldParser parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        if (path.Contains(".txt"))
        {       
            parser.SetDelimiters("|");
        }
        else
        {
            parser.SetDelimiters(",");
        }
        parser.HasFieldsEnclosedInQuotes = true;
        bool firstLine = true;
        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (firstLine)
            {
                  foreach (var val in fields)
                  {
                      dt.Columns.Add(val);
                  }
                  firstLine = false;
                  continue;
             }
             dt.Rows.Add(fields);
          }
     }
 lblCount.Text = "Count of total rows in the file: " + dt.Rows.Count.ToString();
 dgvTextFieldParser1.DataSource = dt;

我希望从文件中读取分隔符,而不是根据文件类型手动传递分隔符。

该如何实现?


除了文件扩展名之外,您如何知道哪个字符是分隔符? - 321polorex123
你能统计每个字符的出现次数,并假设最频繁出现的字符是分隔符吗? - Henry
这只是我用来读取几个文件的示例而已。我的目标是通过读取数据自动检测分隔符(不基于文件类型)。 - AMeh
@Henry 如果一个文件中的数据被引号("")括起来,那么最频繁出现的将是引号而不是分隔符。 - AMeh
你可以使用Henry的方法,如果所谓的分隔符字符在引号内(例如使用RegEx),则可以进行异常处理。 - 321polorex123
4个回答

2

数学上是正确的,但完全没有用的答案:不可能。

实用的答案是:它是可能的,但取决于您对文件结构的了解程度。这归结为一堆假设,根据我们做出的假设不同,答案也会有所不同。如果您无法做出任何假设,那么......请参见数学上正确的答案。

例如,我们可以假设分隔符是以下元素中的一个或多个吗?

List<char> delimiters = new List<char>{' ', ';', '|'};

我们能假设分隔符是使元素长度相等的吗?

我们应该尝试找到一个单个字符作为定界符,还是可以使用单词作为定界符?

等等。

根据问题,我将假设第一种选项,我们有一组有限的可能字符,其中精确地一个是给定文件的定界符。

你可以统计每个这样的字符出现的次数,并假设出现频率最高的字符是定界符吗?这已经足够严格了,还是你需要更加确定?

List<char> delimiters = new List<char>{' ', ';', '-'};
Dictionary<char, int> counts = delimiters.ToDictionary(key => key, value => 0);
foreach(char c in delimiters)
  counts[c] = textArray.Count(t => t == c);

我现在没有电脑无法验证,但最后一步是从字典中返回最大的

需要考虑一种特殊情况,即没有检测到分隔符,两种类型的分隔符数量相等。


我通常会遇到的常见分隔符是一组List<char> delimiters = new List<char>{' ', ';', '-', '|', ','}; - AMeh
@AMeh 我认为这个想法是,大多数分隔格式不会限制字段内容中出现其他分隔符,因此例如你可以在一个以;分隔的文本字段中包含,|,并且所有都将被检测到。你可以尝试基于频率提出一些启发式方法,但是没有额外信息,我不确定你是否能够确定。您能否详细说明为什么需要自动检测它?数据源是否无法提供附加信息以与其一起存储? - ryachza
@AMeh,我不确定你所说的“性能”是什么意思 - 你想要优化什么?关于读取不同类型的文件 - 你从哪里获取正在读取的数据?我希望你的解析器能够针对(格式,数据)对进行操作,而不是每次读取数据时都尝试重新检测格式。 - ryachza
@ryachza 我猜OP想要展示数据,需要一些设施使得他更容易分隔读取的文件。所以优化不在于算法,而是不需要手动调查文件(大概是这样)。他的意思可能是自动化而不是优化 - Konrad Viltersten
@SamAxe 你说得对。这是我在回答中讨论的假设之二。然而,为了有所帮助,我假设OP想要一些“足够好”的东西,简单地检查哪个字符在三个字符集中经常使用。显然,对于这种情况,我做得很对。但是,正如我先前所说 - 你说得对,还有很多改进的空间。记住YAGNI,伙计。 - Konrad Viltersten
显示剩余3条评论

2
非常简单的猜测方法,使用LINQ:
static class CsvSeperatorDetector
{
    private static readonly char[] SeparatorChars = {';', '|', '\t', ','};

    public static char DetectSeparator(string csvFilePath)
    {
        string[] lines = File.ReadAllLines(csvFilePath);
        return DetectSeparator(lines);
    }

    public static char DetectSeparator(string[] lines)
    {
        var q = SeparatorChars.Select(sep => new
                {Separator = sep, Found = lines.GroupBy(line => line.Count(ch => ch == sep))})
            .OrderByDescending(res => res.Found.Count(grp => grp.Key > 0))
            .ThenBy(res => res.Found.Count())
            .First();

        return q.Separator;
    }
}

这个功能会逐行读取文件(请注意,CSV文件可能包含换行符),然后检查每个潜在的分隔符在每一行中出现的次数。接着,我们检查哪个分隔符在大多数行中出现,并且在出现次数相同的情况下,选择分布最均匀的一个(例如,在每一行都出现5次的分隔符比在一行中出现1次,在另一行中出现10次的分隔符更优)。 当然,你可能需要根据自己的需求进行调整,添加错误处理、回退逻辑等等。我相信它并不完美,但对我来说已经足够好了。

我使用了这个解决方案,它工作得很好,除了我发现的一个小问题。当所有分隔符的 Count == 0 时,它会返回列表中的第一个分隔符。在使用该解决方案时需要考虑到这一点。 - Mariusz

0

你可以从文件中取n个字节,使用哈希映射/字典计算可能的分隔符字符(或所有找到的字符),然后最常重复出现的字符可能就是你要找的分隔符。对我来说,使用作为分隔符的字符最多的那些字符是有意义的。完成后,你需要重置流,但由于你正在使用文本读取器,所以可能需要初始化另一个文本读取器之类的东西。如果CSV使用多个分隔符,这将变得稍微棘手一些。你可能需要忽略一些字母和数字等字符。


0
在Python中,我们可以轻松地使用CSV嗅探器来实现这一点。它将适用于文本文件,也适用于仅需要从文件中读取一些字节的情况。

这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - eglease

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接