【问题标题】:parsing of a string containing an array解析包含数组的字符串
【发布时间】:2011-12-19 04:21:06
【问题描述】:

我想将包含递归字符串数组的字符串转换为深度为一的数组。

例子:

StringToArray("[a, b, [c, [d, e]], f, [g, h], i]") == ["a", "b", "[c, [d, e]]", "f", "[g, h]", "i"]

看起来很简单。但是,我来自功能背景,我对 .NET Framework 标准库不太熟悉,所以每次(我从头开始 3 次)我最终都只是简单丑陋的代码。我的最新实现是here。如您所见,它丑得要命。

那么,执行此操作的 C# 方法是什么?

【问题讨论】:

  • +1 解决具有挑战性的问题。但是,我认为这通常用于 codereview:codereview.stackexchange.com/faq#questions。

标签: c# string list


【解决方案1】:

@ojlovecd 有一个很好的答案,使用正则表达式。
但是,他的答案过于复杂,所以这是我的类似,更简单的答案。

public string[] StringToArray(string input) {
    var pattern = new Regex(@"
        \[
            (?:
            \s*
                (?<results>(?:
                (?(open)  [^\[\]]+  |  [^\[\],]+  )
                |(?<open>\[)
                |(?<-open>\])
                )+)
                (?(open)(?!))
            ,?
            )*
        \]
    ", RegexOptions.IgnorePatternWhitespace);

    // Find the first match:
    var result = pattern.Match(input);
    if (result.Success) {
        // Extract the captured values:
        var captures = result.Groups["results"].Captures.Cast<Capture>().Select(c => c.Value).ToArray();
        return captures;
    }
    // Not a match
    return null;
}

使用此代码,您将看到StringToArray("[a, b, [c, [d, e]], f, [g, h], i]") 将返回以下数组:["a", "b", "[c, [d, e]]", "f", "[g, h]", "i"]

有关我用于匹配平衡大括号的平衡组的更多信息,请查看Microsoft's documentation

更新
根据 cmets,如果您还想平衡报价,这里有一个可能的修改。 (请注意,在 C# 中," 被转义为 "")我还添加了该模式的描述以帮助澄清它:

    var pattern = new Regex(@"
        \[
            (?:
            \s*
                (?<results>(?:              # Capture everything into 'results'
                    (?(open)                # If 'open' Then
                        [^\[\]]+            #   Capture everything but brackets
                        |                   # Else (not open):
                        (?:                 #   Capture either:
                            [^\[\],'""]+    #       Unimportant characters
                            |               #   Or
                            ['""][^'""]*?['""] #    Anything between quotes
                        )  
                    )                       # End If
                    |(?<open>\[)            # Open bracket
                    |(?<-open>\])           # Close bracket
                )+)
                (?(open)(?!))               # Fail while there's an unbalanced 'open'
            ,?
            )*
        \]
    ", RegexOptions.IgnorePatternWhitespace);

【讨论】:

  • 这是一个很棒的解决方案。 :)
  • 谢谢,希望我没有抢走你的风头:)
  • 当然不是。只有讨论和改进。 :)
  • 您的解决方案很漂亮。我终于找到时间研究了它,这真的很棒。只有一个问题:现在我希望字符串也被保留为原子实体,即使它们包含列表(所以:“[a, \"b, [c, d]\", e]" => ["a" , "b, [c, d]", "e"]),但引号没有开始和结束引号的区别,因此平衡组不起作用。你也有一个优雅的解决方案吗? :-)
  • 平衡引号——把扳手扔进引擎的方法! I did this in JavaScript recently,但该解决方案使用正则表达式作为解析引擎,因此它不是纯正则表达式解决方案。看看可能会很有趣。
【解决方案2】:

使用正则表达式,它可以解决您的问题:

static string[] StringToArray(string str)
{
    Regex reg = new Regex(@"^\[(.*)\]$");
    Match match = reg.Match(str);
    if (!match.Success)
        return null;
    str = match.Groups[1].Value;
    List<string> list = new List<string>();
    reg = new Regex(@"\[[^\[\]]*(((?'Open'\[)[^\[\]]*)+((?'-Open'\])[^\[\]]*)+)*(?(Open)(?!))\]");
    Dictionary<string, string> dic = new Dictionary<string, string>();
    int index = 0;
    str = reg.Replace(str, m =>
    {
        string temp = "ojlovecd" + (index++).ToString();
        dic.Add(temp, m.Value);
        return temp;
    });
    string[] result = str.Split(',');
    for (int i = 0; i < result.Length; i++)
    {
        string s = result[i].Trim();
        if (dic.ContainsKey(s))
            result[i] = dic[s].Trim();
        else
            result[i] = s;
    }
    return result;
}

【讨论】:

  • 我也认为 Regex 是 的方式,但这行不通,因为您需要捕获“平衡”大括号。
  • @ScottRippey 嗨,Scott,我修改了我的代码,请试一试。
  • 看起来不错。需要一些清理,但我认为它有效:) 对于其他对这些“平衡组”感兴趣的人,尤其是平衡大括号匹配,你应该看看Microsoft's documentation on "Balancing Group Definitions".
【解决方案3】:

老实说,我只是在 F# 程序集中编写此方法,因为它可能更容易。如果您查看 C# 中的 JavaScriptSerializer 实现(使用 dotPeek 或反射器之类的反编译器),您会发现对于 JSON 中的类似数组,数组解析代码是多么混乱。当然,这必须处理更多不同的令牌数组,但你明白了。

这是他们的DeserializeList 实现,比它通常的 dotPeek 的反编译版本更丑,不是原始版本,但你明白了。 DeserializeInternal 将递归到子列表。

private IList DeserializeList(int depth)
{
  IList list = (IList) new ArrayList();
  char? nullable1 = this._s.MoveNext();
  if (((int) nullable1.GetValueOrDefault() != 91 ? 1 : (!nullable1.HasValue ? 1 : 0)) != 0)
    throw new ArgumentException(this._s.GetDebugString(AtlasWeb.JSON_InvalidArrayStart));
  bool flag = false;
  char? nextNonEmptyChar;
  char? nullable2;
  do
  {
    char? nullable3 = nextNonEmptyChar = this._s.GetNextNonEmptyChar();
    if ((nullable3.HasValue ? new int?((int) nullable3.GetValueOrDefault()) : new int?()).HasValue)
    {
      char? nullable4 = nextNonEmptyChar;
      if (((int) nullable4.GetValueOrDefault() != 93 ? 1 : (!nullable4.HasValue ? 1 : 0)) != 0)
      {
        this._s.MovePrev();
        object obj = this.DeserializeInternal(depth);
        list.Add(obj);
        flag = false;
        nextNonEmptyChar = this._s.GetNextNonEmptyChar();
        char? nullable5 = nextNonEmptyChar;
        if (((int) nullable5.GetValueOrDefault() != 93 ? 0 : (nullable5.HasValue ? 1 : 0)) == 0)
        {
          flag = true;
          nullable2 = nextNonEmptyChar;
        }
        else
          goto label_8;
      }
      else
        goto label_8;
    }
    else
      goto label_8;
  }
  while (((int) nullable2.GetValueOrDefault() != 44 ? 1 : (!nullable2.HasValue ? 1 : 0)) == 0);
  throw new ArgumentException(this._s.GetDebugString(AtlasWeb.JSON_InvalidArrayExpectComma));
 label_8:
  if (flag)
    throw new ArgumentException(this._s.GetDebugString(AtlasWeb.JSON_InvalidArrayExtraComma));
  char? nullable6 = nextNonEmptyChar;
  if (((int) nullable6.GetValueOrDefault() != 93 ? 1 : (!nullable6.HasValue ? 1 : 0)) != 0)
    throw new ArgumentException(this._s.GetDebugString(AtlasWeb.JSON_InvalidArrayEnd));
  else
    return list;
}

递归解析在 C# 中的管理不如在 F# 中那样好。

【讨论】:

    【解决方案4】:

    没有真正的“标准”方式来做到这一点。请注意,如果您想考虑所有可能性,实现可能会变得非常混乱。我会推荐一些递归的东西,比如:

        private static IEnumerable<object> StringToArray2(string input)
        {
            var characters = input.GetEnumerator();
            return InternalStringToArray2(characters);
        }
    
        private static IEnumerable<object> InternalStringToArray2(IEnumerator<char> characters)
        {
            StringBuilder valueBuilder = new StringBuilder();
    
            while (characters.MoveNext())
            {
                char current = characters.Current;
    
                switch (current)
                {
                    case '[':
                        yield return InternalStringToArray2(characters);
                        break;
                    case ']':
                        yield return valueBuilder.ToString();
                        valueBuilder.Clear();
                        yield break;
                    case ',':
                        yield return valueBuilder.ToString();
                        valueBuilder.Clear();
                        break;
                    default:
                        valueBuilder.Append(current);
                        break;
                }
    

    虽然您不限于递归,并且总是可以退回到像

    这样的单一方法
        private static IEnumerable<object> StringToArray1(string input)
        {
            Stack<List<object>> levelEntries = new Stack<List<object>>();
            List<object> current = null;
            StringBuilder currentLineBuilder = new StringBuilder();
    
            foreach (char nextChar in input)
            {
                switch (nextChar)
                {
                    case '[':
                        levelEntries.Push(current);
                        current = new List<object>();
                        break;
                    case ']':
                        current.Add(currentLineBuilder.ToString());
                        currentLineBuilder.Clear();
                        var last = current;
                        if (levelEntries.Peek() != null)
                        {
                            current = levelEntries.Pop();
                            current.Add(last);
                        }
                        break;
                    case ',':
                        current.Add(currentLineBuilder.ToString());
                        currentLineBuilder.Clear();
                        break;
                    default:
                        currentLineBuilder.Append(nextChar);
                        break;
                }
            }
    
            return current;
        }
    

    你觉得好闻的东西

    【讨论】:

      【解决方案5】:
      using System;
      using System.Text;
      using System.Text.RegularExpressions;
      using Microsoft.VisualBasic.FileIO; //Microsoft.VisualBasic.dll
      using System.IO;
      
      public class Sample {
          static void Main(){
              string data = "[a, b, [c, [d, e]], f, [g, h], i]";
              string[] fields = StringToArray(data);
              //check print
              foreach(var item in fields){
                  Console.WriteLine("\"{0}\"",item);
              }
          }
          static string[] StringToArray(string data){
              string[] fields = null;
              Regex innerPat = new Regex(@"\[\s*(.+)\s*\]");
              string innerStr = innerPat.Matches(data)[0].Groups[1].Value;
              StringBuilder wk = new StringBuilder();
              var balance = 0;
              for(var i = 0;i<innerStr.Length;++i){
                  char ch = innerStr[i];
                  switch(ch){
                  case '[':
                      if(balance == 0){
                          wk.Append('"');
                      }
                      wk.Append(ch);
                      ++balance;
                      continue;
                  case ']':
                      wk.Append(ch);
                      --balance;
                      if(balance == 0){
                          wk.Append('"');
                      }
                      continue;
                  default:
                      wk.Append(ch);
                      break;
                  }
              }
              var reader = new StringReader(wk.ToString());
              using(var csvReader = new TextFieldParser(reader)){
                  csvReader.SetDelimiters(new string[] {","});
                  csvReader.HasFieldsEnclosedInQuotes = true;
                  fields = csvReader.ReadFields();
              }
              return fields;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2020-12-14
        • 1970-01-01
        • 1970-01-01
        • 2019-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多