任意定界符/转义字符处理的最佳算法是啥?

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 =&gt; item.Any())

我认为这对于单一方法来说过于逻辑了;很难跟上。如果有人有时间,我认为最好将其分解为多个方法,也许还有自己的类。

【讨论】:

【参考方案7】:

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

【讨论】:

以上是关于任意定界符/转义字符处理的最佳算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

PHP基础知识

c语言中不按转义字符处理是啥意思

正则表达式1

正则表达式

php的定界符<<<eof的问题

php字符串