#c# #string #list
#c# #строка #Список
Вопрос:
Я хотел бы преобразовать строку, содержащую рекурсивный массив строк, в массив с глубиной один.
Пример:
StringToArray("[a, b, [c, [d, e]], f, [g, h], i]") == ["a", "b", "[c, [d, e]]", "f", "[g, h]", "i"]
Кажется довольно простым. Но я пришел из функционального фона и не очень хорошо знаком со стандартными библиотеками .NET Framework, поэтому каждый раз (я начинал с нуля примерно 3 раза) В итоге я получаю просто уродливый код. Моя последняя реализация находится здесь . Как вы видите, это чертовски уродливо.
Итак, каков способ C # сделать это?
Комментарии:
1. 1 для сложной задачи. Тем не менее, я думаю, что это обычно для codereview: codereview.stackexchange.com/faq#questions .
Ответ №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.
Обновление:
согласно комментариям, если вы хотите также сбалансировать кавычки, вот возможная модификация. (Обратите внимание, что в 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);
Комментарии:
1. Это фантастическое решение. 🙂
2. Спасибо, надеюсь, я не украл ваш гром 🙂
3. Конечно, нет. Есть только обсуждение и улучшение. 🙂
4. Ваше решение прекрасно. Мне наконец удалось найти время и изучить его, и это действительно фантастика. Только одна проблема: теперь я бы хотел, чтобы строки также сохранялись как атомарные объекты, даже если они содержат списки (так: «[a, «b, [c, d] «, e]» => [«a», «b, [c, d]»,»e»]), но кавычки не различают открывающую и закрывающую кавычки, поэтому балансирующие группы не работают. У вас есть элегантное решение и для этого? 🙂
5. Сбалансированные кавычки — способ бросить гаечный ключ в движок! Недавно я сделал это на JavaScript , но в этом решении в качестве механизма синтаксического анализа использовалось регулярное выражение, так что это не было чисто регулярным выражением. Возможно, было бы интересно взглянуть.
Ответ №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 resu<
}
Комментарии:
1. Я также думаю, что регулярное выражение — это способ, но это не сработает, потому что вам нужно захватить «сбалансированные» фигурные скобки.
2. @ScottRippey Привет, Скотт, я изменил свой код, попробуйте, пожалуйста.
3. Это выглядит хорошо. Требуется некоторая очистка, но я предполагаю, что это работает 🙂 Для тех, кто еще интересуется этими «балансирующими группами», особенно для сбалансированного сопоставления фигурных скобок, вам следует ознакомиться с документацией Microsoft по «Балансирующим определениям групп».
Ответ №3:
Честно говоря, я бы просто написал этот метод в сборке F #, поскольку это, вероятно, намного проще. Если вы посмотрите на JavaScriptSerializer
реализацию на C # (с помощью декомпилятора, такого как dotPeek или reflector), вы увидите, насколько запутан код синтаксического анализа массива для аналогичного массива в 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;
}
}