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

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

这是规则:

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

代码签名(基本上是C#)

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

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

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

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

简单的状态机通常是最简单,最快捷的方式。 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 

就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)类的规则。

 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(); 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(); } 

这是我在C#中的移植函数

  public static void smartSplit(string text, char delim, char esc, ref List 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指出我正确的方向。

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

这是一个更惯用和可读的方法:

 public IEnumerable 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())

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