任意定界符/转义字符处理的最佳算法是啥?
Posted
技术标签:
【中文标题】任意定界符/转义字符处理的最佳算法是啥?【英文标题】:What is the best algorithm for arbitrary delimiter/escape character processing?任意定界符/转义字符处理的最佳算法是什么? 【发布时间】:2010-10-14 15:30:03 【问题描述】:我有点惊讶于网络上没有这方面的一些信息,而且我一直发现问题比我想象的要棘手。
规则如下:
-
您从分隔/转义数据开始拆分为一个数组。
分隔符是一个任意字符
转义字符是一个任意字符
分隔符和转义符都可能出现在数据中
正则表达式很好,但最好是性能良好的解决方案
编辑:可以忽略空元素(包括前导或结束分隔符)
代码签名(在 C# 中基本上是)
public static string[] smartSplit(
string delimitedData,
char delimiter,
char escape)
问题最棘手的部分是转义连续转义字符的情况,当然,因为(调用 / 转义字符和,分隔符): ////////, = ////,
我是否错过了在网络上或另一个 SO 问题中处理的某个地方?如果没有,那就用你的大脑袋来工作......我认为这个问题对于公共利益来说是件好事。我自己正在研究它,但还没有一个好的解决方案。
【问题讨论】:
【参考方案1】:简单的状态机通常是最简单、最快的方法。 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
【讨论】:
【参考方案2】: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);
【讨论】:
实际上....这一定没有运行。这当然让我走上了正确的道路,但是,例如,那些中断;语句需要继续;否则循环只会运行一次,并且只会将一个字符添加到数组中。 实际上它已经运行并且可以工作了。中断适用于 switch 语句,而不是 for 循环。【参考方案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】:就 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;
这种实现的优点是可以自然地处理连续的excapes,并且可以很容易地扩展以赋予更多的转义序列特殊的含义(即添加类似ESC(t) token.appeand(TAB)
的规则)。
【讨论】:
【参考方案5】:这是我在 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 为我指明了正确的方向。
【讨论】:
【参考方案6】:这是一种更惯用和更易读的方式:
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())
。
我认为这对于单一方法来说过于逻辑了;很难跟上。如果有人有时间,我认为最好将其分解为多个方法,也许还有自己的类。
【讨论】:
【参考方案7】:您正在寻找类似“字符串标记器”的东西。有一个version,我很快发现它很相似。或者看getopt。
【讨论】:
以上是关于任意定界符/转义字符处理的最佳算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章