【问题标题】:Parsing CSV data using a finite state machine使用有限状态机解析 CSV 数据
【发布时间】:2014-07-19 10:00:43
【问题描述】:

我想读取一个包含逗号分隔值的文件,所以写了一个有限状态机:

private IList<string> Split(string line)
{
    List<string> values = new List<string>();
    string value = string.Empty;
    ParseState state = ParseState.Initial;
    foreach (char c in line)
    {
        switch (state)
        {
            case ParseState.Initial:
                switch (c)
                {
                    case COMMA:
                        values.Add(string.Empty);
                        break;
                    case QUOTE:
                        state = ParseState.Quote;
                        break;
                    default:
                        value += c;
                        state = ParseState.Data;
                        break;
                }
                break;
            case ParseState.Data:
                switch (c)
                {
                    case COMMA:
                        values.Add(value);
                        value = string.Empty;
                        state = ParseState.Initial;
                        break;
                    case QUOTE:
                        throw new InvalidDataException("Improper quotes");
                    default:
                        value += c;
                        break;
                }
                break;
            case ParseState.Quote:
                switch (c)
                {
                    case QUOTE:
                        state = ParseState.QuoteInQuote;
                        break;
                    default:
                        value += c;
                        break;
                }
                break;
            case ParseState.QuoteInQuote:
                switch (c)
                {
                    case COMMA:
                        values.Add(value);
                        value = string.Empty;
                        state = ParseState.Initial;
                        break;
                    case QUOTE:
                        value += c;
                        state = ParseState.Quote;
                        break;
                    default:
                        throw new InvalidDataException("Unpaired quotes");
                }
                break;
        }
    }

    switch (state)
    {
        case ParseState.Initial:
        case ParseState.Data:
        case ParseState.QuoteInQuote:
            values.Add(value);
            break;
        case ParseState.Quote:
            throw new InvalidDataException("Unclosed quotes");
    }

    return values;
}

是的,我知道关于 CSV 解析器的建议是“不要自己编写”,但是

  1. 我很快就需要它,并且
  2. 我们工作中的下载政策需要几天时间才能让我 从网络上获取开源。

嘿,至少我没有以string.Split() 开头,或者更糟糕的是,尝试使用Regex

是的,我知道可以通过使用StringBuilder 来改进它,而且它对数据中的引号有限制,但是

  1. 性能不是问题,并且
  2. 这只是为了在内部生成定义明确的测试数据,

所以我不在乎那些。

关心的是末尾明显的尾随块,用于在最后一个逗号之后清除所有数据,以及它开始看起来像某种反模式的方式在那里,这正是 FSM 等“好”模式应该避免的那种事情。

所以我的问题是:最后的这个块是某种反模式吗,它是不是将来会回来咬我?

【问题讨论】:

  • 您尝试过.net 框架附带的TextFieldParser 吗?我不确定这会对你有帮助,我没用过。
  • 这可能属于 codereview.se
  • 他们是否能够在不对整个方法进行全面审查的情况下将答案限制在我的问题上?
  • 只是为了碰撞@SriramSakthivel:TextFieldParser 是一个非常好的、简单的解决方案。是的,您需要添加对属于 .Net 的 VB 程序集的引用,这感觉不自然但完全没问题;我有一个使用这种方法的大型生产应用程序。如果您在工作中设法绕过下载限制,CsvHelper (github.com/JoshClose/CsvHelper) 非常棒 - 它现在是我们处理任何与 CSV 相关的标准工具。
  • @Frans CsvHelper 本来是我的首选,而且我确实已经向技术服务部门提出了让我下载它的请求,但是程序就是程序……

标签: c# parsing csv


【解决方案1】:

我见过的所有 FSM(不是我去寻找它们,请注意)都有某种“清理”步骤,这仅仅是由于枚举的性质。

在 FSM 中,您总是根据当前状态执行操作,然后为下一次迭代重置“当前状态”,因此,一旦您的迭代结束,您必须执行最后一个操作以执行操作“当前状态”。 (最好将其视为作用于“先前状态”并设置“当前状态”)。

因此,我认为你所做的是模式的一部分

但是你为什么不尝试一些关于 SO 的其他答案呢?

适应的解决方案,仍然是 FSM:

public IEnumerable<string> fsm(string s)
{
    int i, a = 0, l = s.Length;
    var q = true;
    for (i = 0; i < l; i++)
    {
        switch (s[i])
        {
            case ',':
                if (q)
                {
                    yield return s.Substring(a, i - a).Trim();
                    a = i + 1;
                }
                break;

            // pick your flavor
            case '"':
            //case '\'':
                q = !q;
                break;
        }
    }
    yield return s.Substring(a).Trim();
}

// === usage ===
var row = fsm(csvLine).ToList();
foreach(var column in fsm(csvLine)) { ... }

【讨论】:

    【解决方案2】:

    在 FSM 中,您可以确定哪些状态是允许的停止状态。因此,在典型的实现中,当您退出循环时,您至少需要检查以确保您的最后一个状态是允许的停止状态之一,否则会引发堵塞错误。因此,在循环之外进行最后一次状态检查是模式的一部分。

    【讨论】:

      【解决方案3】:

      问题的根源,如果你想这么称呼它,就是你的输入数据中没有行尾标记。例如,在输入字符串的末尾添加一个换行符,您将能够摆脱似乎让您非常烦恼的“尾随块”。

      就我而言,您的代码是正确的,而且,不,这个实现没有理由在将来会回来咬您!

      【讨论】:

        【解决方案4】:

        我遇到了类似的问题,但我正在逐个字符地解析文本文件。在 while 循环之后,我不喜欢这个大的清理开关块。为了解决这个问题,我为流式阅读器制作了一个包装器。当流式阅读器没有剩余字符时,包装器检查。在这种情况下,包装器将返回一次 EOT-ascii 字符(EOT 等于 EOF)。这样,我的状态机就可以根据当时所处的状态对 EOF 做出反应。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-01-07
          • 2011-03-06
          • 2016-07-21
          • 2011-02-16
          • 1970-01-01
          • 1970-01-01
          • 2013-08-24
          • 1970-01-01
          相关资源
          最近更新 更多