任意分隔符/转义字符处理的最佳算法是什么?

时间:2009-03-20 20:14:44

标签: c# regex algorithm

我有点惊讶的是,网上没有关于此的信息,我一直发现这个问题比我想象的要小一些。

以下是规则:

  1. 您将从分隔/转义数据开始拆分为数组。
  2. 分隔符是一个任意字符
  3. 转义字符是一个任意字符
  4. 分隔符和转义字符都可能出现在数据
  5. 正则表达式很好,但最好是性能良好的解决方案
  6. 编辑:可以忽略空元素(包括前导或结尾分隔符)
  7. 代码签名(基本上是C#)

    public static string[] smartSplit(
                             string delimitedData, 
                             char delimiter, 
                             char escape) {}
    

    问题中最棘手的部分是转义的连续转义字符大小写,当然,因为(调用/转义字符和分隔符):////////,= ////,

    我错过了某个地方,这是在网上处理还是在另一个SO问题中处理?如果没有,那就把你的大脑投入工作......我认为这个问题对于公益事业来说是件好事。我自己正在研究它,但还没有一个好的解决方案。

7 个答案:

答案 0 :(得分:6)

简单的状态机通常是最简单,最快捷的方式。 Python中的示例:

def extract(input, delim, escape):
  # states
  parsing = 0
  escaped = 1

  state = parsing
  found = []
  parsed = ""

  for c in input:
    if state == parsing:
      if c == delim:
        found.append(parsed)
        parsed = ""
      elif c == escape:
        state = escaped
      else:
        parsed += c
    else: # state == escaped
       parsed += c
       state = parsing

  if parsed:
    found.append(parsed)

  return found

答案 1 :(得分:5)

void smartSplit(string const& text, char delim, char esc, vector<string>& tokens)
{
    enum State { NORMAL, IN_ESC };
    State state = NORMAL;
    string frag;

    for (size_t i = 0; i<text.length(); ++i)
    {
        char c = text[i];
        switch (state)
        {
        case NORMAL:
            if (c == delim)
            {
                if (!frag.empty())
                    tokens.push_back(frag);
                frag.clear();
            }
            else if (c == esc)
                state = IN_ESC;
            else
                frag.append(1, c);
            break;
        case IN_ESC:
            frag.append(1, c);
            state = NORMAL;
            break;
        }
    }
    if (!frag.empty())
        tokens.push_back(frag);
}

答案 2 :(得分:3)

就FSM而言,这种标记器的实现是相当直接的。

你确实做了一些决定(比如,我如何处理前导分隔符?剥离或发出NULL令牌)。


这是一个抽象版本,它忽略了前导和多个分隔符,并且不允许转义换行符:

state(input)     action
========================
BEGIN(*):         token.clear(); state=START;
END(*):           return;
*(\n\0):          token.emit(); state=END;
START(DELIMITER): ; // NB: the input is *not* added to the token!
START(ESCAPE):    state=ESC; // NB: the input is *not* added to the token!
START(*):         token.append(input); state=NORM;
NORM(DELIMITER):  token.emit(); token.clear(); state=START;
NORM(ESCAPE):     state=ESC; // NB: the input is *not* added to the token!
NORM(*):          token.append(input);
ESC(*):           token.append(input); state=NORM;

这种实现具有自然处理连续excape的优点,并且可以很容易地扩展为更多转义序列赋予特殊含义(即添加ESC(t) token.appeand(TAB)之类的规则。

答案 3 :(得分:3)

private static string[] Split(string input, char delimiter, char escapeChar, bool removeEmpty)  
    {  
        if (input == null)
        {
            return new string[0];
        }

        char[] specialChars = new char[]{delimiter, escapeChar};

        var tokens = new List<string>();
        var token = new StringBuilder();

        for (int i = 0; i < input.Length; i++)
        {
            var c = input[i];

            if (c.Equals(escapeChar))
            {
                if (i >= input.Length - 1)
                {
                    throw new ArgumentException("Uncompleted escape sequence has been encountered at the end of the input");
                }

                var nextChar = input[i + 1];

                if (nextChar != escapeChar && nextChar != delimiter)
                {
                    throw new ArgumentException("Unknown escape sequence has been encountered: " + c + nextChar);
                }

                token.Append(nextChar);
                i++;
            }
            else if (c.Equals(delimiter))
            {
                if (!removeEmpty || token.Length > 0)
                {
                    tokens.Add(token.ToString());
                    token.Length = 0;
                }
            }
            else
            {
                var index = input.IndexOfAny(specialChars, i);

                if (index < 0)
                {
                    token.Append(c);
                }
                else
                {
                    token.Append(input.Substring(i, index - i));
                    i = index - 1;
                }
            }
        }

        if (!removeEmpty || token.Length > 0)
        {
            tokens.Add(token.ToString());
        } 

        return tokens.ToArray();
    }

答案 4 :(得分:2)

这是我在C#中的移植功能

    public static void smartSplit(string text, char delim, char esc, ref List<string> listToBuild)
    {
        bool currentlyEscaped = false;
        StringBuilder fragment = new StringBuilder();

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (currentlyEscaped)
            {
                fragment.Append(c);
                currentlyEscaped = false;
            }
            else 
            {
                if (c == delim)
                {
                    if (fragment.Length > 0)
                    {
                        listToBuild.Add(fragment.ToString());
                        fragment.Remove(0, fragment.Length);
                    }

                }
                else if (c == esc)
                    currentlyEscaped = true;
                else
                    fragment.Append(c);
            }
        }

        if (fragment.Length > 0)
        {
            listToBuild.Add(fragment.ToString());
        }
    }

希望这可以帮助将来的某个人。感谢KenE指出我正确的方向。

答案 5 :(得分:1)

你正在寻找类似“字符串标记器”的东西。有一个version我很快发现了类似的东西。或者查看getopt

答案 6 :(得分:1)

这是一种更惯用且可读的方式:

public IEnumerable<string> SplitAndUnescape(
    string encodedString,
    char separator,
    char escape)
{
    var inEscapeSequence = false;
    var currentToken = new StringBuilder();

    foreach (var currentCharacter in encodedString)
        if (inEscapeSequence)
        {
            currentToken.Append(currentCharacter);
            inEscapeSequence = false;
        }
        else
            if (currentCharacter == escape)
                inEscapeSequence = true;
            else
                if (currentCharacter == separator)
                {
                    yield return currentToken.ToString();
                    currentToken.Clear();
                }
                else
                    currentToken.Append(currentCharacter);

    yield return currentToken.ToString();
}

请注意,这不会删除空元素。我不认为这应该是解析器的责任。如果要删除它们,只需在结果上调用Where(item => item.Any())

我认为这对于单一方法来说太过逻辑;它很难遵循。如果有人有时间,我认为最好将其分解为多种方法,也许是自己的类。