2011-11-01 18 views
8

文字列の再帰配列を含む文字列を深さ1の配列に変換したいと思います。配列を含む文字列の解析

例:

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

1:私もそれを明確に支援するためのパターンの記述を追加(C#で"""としてエスケープされていることに注意してください)。しかし、私はこれがcodereviewのために典型的だと思う:codereview.stackexchange.com/faq#questions。 –

答えて

5

@ojlovecdは、正規表現を使用してよい答えを得ています。
しかし、彼の答えはあまりにも複雑なので、ここで私の同様の簡単な答えです。 ["a", "b", "[c, [d, e]]", "f", "[g, h]", "i"]

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]")には、以下の配列を返しますことがわかります。

バランスの取れたブレースのマッチングに使用したバランスのとれたグループの詳細については、Microsoft's documentationを参照してください。

更新
コメントを1として あなたも引用符のバランスをとるにしたい場合は、ここで可能な変更です。困難な問題のため

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); 
+0

これは素晴らしいソリューションです。 :) – ojlovecd

+0

ありがとう、私はあなたの雷を盗んでいないことを願って:) –

+0

確かにではありません。議論と改善だけがあります。 :) – ojlovecd

0

正直言って、私はこのメソッドをF#アセンブリに書くほうがはるかに簡単です。 C#のJavaScriptSerializer実装(dotPeekやリフレクターのようなデコンパイラを使用)を見ると、配列解析コードがJSONの類似の配列にどれほど難しいかが分かります。これははるかに多様なトークンの配列を扱わなければならないと考えていますが、あなたはその考えを得ています。

ここにはDeserializeListの実装がありますが、それは元々ではなく、元のドットパイの逆コンパイルされたバージョンよりも醜いですが、あなたはそのアイデアを得ています。 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#と同じように再帰的な解析は管理されません。

0

これを行うための実際の「標準的な」方法はありません。すべての可能性を考慮したい場合、実装がかなり乱雑になることに注意してください。あなたは再帰に制限し、常に正規表現とのあなたに戻っていい匂いが何であれ

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

のような単一の方法に

2

落ちることができませんが、それを

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

:私のような再帰的なものをお勧めしますあなたの問題を解決することができます:

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; 
} 
+0

私はRegexがやり方であるとも思っていますが、これはうまくいかないでしょう。なぜならあなたは "バランスの取れた"中括弧を捕捉する必要があるからです。 –

+0

@ScottRippeyこんにちは、スコット、私は自分のコードを変更してください、してみてください。 – ojlovecd

+0

それはよさそうだ。いくつかのクリーンアップが必要ですが、私はそれが動作すると仮定します:)これらの "バランシンググループ"に興味のある人、特にバランスの取れたブレースマッチングのためには、[Microsoftのドキュメント "Balancing Group Definitions"を見てください。 ://msdn.microsoft.com/en-us/library/bs2twtah.aspx#balancing_group_definition) –

0
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; 
    } 
} 
関連する問題