C # один к многим помощь разбора строки с помощью регулярных выражений лямбда - PullRequest
2 голосов
/ 21 июня 2011

Я знаю, как сделать это, используя очевидный способ с string.split ().

Я ищу более элегантный и быстродействующий код, возможно, с использованием регулярных выражений и / или linq / lambdas.

Если моя входная строка похожа на эту: *

Суть: одна непрерывная цепочка классов и учеников. GradeId - это int, имя студента - строка. Оценки, разделенные запятой и имя ученика, разделенные звездочкой.

Возможно, в определенном классе нет учеников, как в «1: stud1 * stud2 * stud3,2,3» Здесь во 2 и 3 классах нет учеников. Только в 1 классе учатся 3 ученика. Моя цель - собрать коллекцию, где я мог бы

foreach(Grade g in mycollection)
{ 
  foreach (int i = 0; i < g.studentnames.length; i++)
     console.writeline( g.StudentNames[i] ) 
}

class Grade { int gradeid, string[] studentnames } 

regex и linq Gurus, пожалуйста, сообщите. спасибо

Ответы [ 6 ]

3 голосов
/ 21 июня 2011

Предполагая, что ваш формат данных соответствует описанию, это может быть то, что вы ищете? Вам все еще, вероятно, следует использовать String.Split () для работы с вводом, так как это список с разделителями строк, но вы можете, по крайней мере, сделать его анонимной типизированной коллекцией.

string input = "10:name1*20:name2*30:name3*40:name4*50:name5";

var data =
(
    from pair in input.Split( '*' )
    let student = pair.Split( ':' )
    select new { Grade = int.Parse( student[ 0 ] ), Name = student[ 1 ] }
);

foreach( var student in data )
{
    Console.WriteLine( student );
}

Edit:

Кажется, у вас отношения 1: много -> ученик? Возможно, вам стоит изучить коллекцию Lookup , чтобы легко получить всех учеников с оценкой N.

string input = "10:name1.1*name1.2*name1.3,20:name2.1*name2.2*,30:name3.1,40:name4.1*name4.2*name4.3,50:name5.1";

var studentData = ( Lookup<int,string[]> )
(
    from 
        line in input.Split( ',' )
    where 
        line.IndexOf( ':' ) > -1
    let 
        grade = line.Substring( 0, line.IndexOf( ':' ) )
    let 
        names = line.Remove( 0, grade.Length + 1 ).Split( '*' )
    select 
        new { Grade = int.Parse( grade ), Students = names }
).ToLookup( s => s.Grade, s => s.Students );

foreach( IGrouping<int,string[]> gradeSet in studentData )
{
    Console.WriteLine( gradeSet.Key );
    Console.WriteLine( studentData[ gradeSet.Key ] );
}

Кроме того, я понимаю, что это не «linqy-est» решение, но, надеюсь, оно облегчит вашу работу.

2 голосов
/ 21 июня 2011

Редактировать: теперь используется новая входная строка OP.

string mystring = "GradeId1:StudentName1*StudentName2*StudentNameN,GradeId2:StudentName1*StudentName2*StudentNameN,GradeIdN:Student1*StudentName2*StudentNameN";
MatchCollection matches = 
   Regex.Matches(
      mystring,
      @"(?:GradeId(\w+)(?:(?=,)|\:(?:([\w ]+)(?:$|\*))*))");

var grades = matches.Cast<Match>().Select(
   gradeMatch => 
      new
      {
         Grade = gradeMatch.Groups[1].Value,
         Students = gradeMatch.Groups[2].Captures
            .Cast<Capture> ()
            .Select (c => c.Value).ToList ()
      });

foreach (var grade in grades)
{
   Console.WriteLine("Grade: " + grade.Grade);
   foreach (string student in grade.Students)
      Console.WriteLine("   " + student);
}

Для этой строки GradeId1:StudentName1*StudentName2*StudentNameN,GradeId2,GradeIdN:Student1*StudentName2*StudentNameN производит такой вывод:

Grade: 1
   StudentName1
   StudentName2
Grade: 2
Grade: N
   Student1
   StudentName2
   StudentNameN

Для заинтересованных:

match[0].Value => GradeId1:StudentName1*StudentName2*
match[0].Groups[0].Value => GradeId1:StudentName1*StudentName2*
match[0].Groups[0].Captures[0].Value => GradeId1:StudentName1*StudentName2*
match[0].Groups[1].Value => 1
match[0].Groups[1].Captures[0].Value => 1
match[0].Groups[2].Value => StudentName2
match[0].Groups[2].Captures[0].Value => StudentName1
match[0].Groups[2].Captures[1].Value => StudentName2
match[1].Value => GradeId2
match[1].Groups[0].Value => GradeId2
match[1].Groups[0].Captures[0].Value => GradeId2
match[1].Groups[1].Value => 2
match[1].Groups[1].Captures[0].Value => 2
match[1].Groups[2].Value =>
match[2].Value => GradeIdN:Student1*StudentName2*StudentNameN
match[2].Groups[0].Value => GradeIdN:Student1*StudentName2*StudentNameN
match[2].Groups[0].Captures[0].Value => GradeIdN:Student1*StudentName2*StudentNameN
match[2].Groups[1].Value => N
match[2].Groups[1].Captures[0].Value => N
match[2].Groups[2].Value => StudentNameN
match[2].Groups[2].Captures[0].Value => Student1
match[2].Groups[2].Captures[1].Value => StudentName2
match[2].Groups[2].Captures[2].Value => StudentNameN
2 голосов
/ 21 июня 2011

OpticalDelusion прав в том, что Linq определенно ухудшит производительность. В общем, Linq удобен, но не быстр.

Regex бесполезен для фактического разбора в сложных случаях разбиения строк, таких как этот, - он более полезен для поиска конкретного образца в произвольной строке или внесения в белый список строки. Поэтому, если вы хотите убедиться, что входная строка имеет правильный формат, вы можете использовать шаблон регулярного выражения, например:

"^([a-zA-Z0-9]+:[a-zA-Z0-9]+(\*[a-zA-Z0-9])*)(,[a-zA-Z0-9]+:[a-zA-Z0-9]+(\*[a-zA-Z0-9])?)*$"

Как правило, любой символ или цифра, один или несколько раз, за ​​которыми следует двоеточие, затем другая последовательность букв или цифр, а затем '*' и другая последовательность букв или цифр 0 или более раз. Затем это повторяется 0 или более раз.

Убедившись, что строка в правильном формате, вы можете выполнять операции string.split ().

1 голос
/ 21 июня 2011

Вот ответ с использованием одной (длинной) строки Linq (я предпочитаю использовать методы расширения напрямую, но вы также можете использовать короткий синтаксис Linq).Я не уверен, что использование Linq / extensions более «изящно» или более просто, чем делать это длинными руками с вложенными ifs и т.п.Признаюсь, есть что-то классное в прекрасном длинном выражении Linq, которое выполняет сложную работу.

string input = "1:A*B*C,2:A*B,3:B*C*D";
var grades = input
  .Split(',')
  .Select(x => x.Split(':'))
  .Select(x => x[1].Split('*').Select(n => new { GradeId = x[0], StudentName = n }))
  .SelectMany(x => x)
  .ToList();

Это создает List<T> анонимных типов с полями GradeId и StudentName для всех комбинаций.

Редактировать: пересмотренный вопрос немного легче.Вот как вы можете получить вложенные списки по запросу, используя эту технику:

var grades = input
  .Split(',')
  .Select(x => x.Split(':'))
  .Select(x => new { GradeId = x[0], StudentNames = x[1].Split('*').ToList() })
  .ToList();

Затем вы можете выполнить итерацию следующим образом:

foreach(var grade in grades)
{
  //You could always use a foreach here too
  for(int i = 0; i < grade.StudentNames.Length ; i++)
  {
    Console.WriteLine(grade.StudentNames[i]);
  }
}
0 голосов
/ 21 июня 2011

По моему опыту, String.Split (), как правило, является наилучшим вариантом в большинстве случаев, когда он работает.Единственным исключением является случай, когда вы имеете дело с очень большими блоками текста, которые не могут быть прочитаны по одной строке за раз (или аналогично), так что атака по нему с помощью Split () в итоге приведет к переполнению кучи, заполненной большими массивами строк.

В этих случаях вы можете создать композицию блоков перечислителя.Внутри них может быть цикл, который использует String.IndexOf () для поиска последовательных разделителей, а затем использует Substring () для вытягивания и выдачи текста между ними.Это помогает ограничить количество строк, находящихся в куче, в любой момент, но не следует рассматривать строку как IEnumerable (что, как мне кажется, не так хорошо работает).

Для этоговажно, что может быть достаточно просто использовать один такой блок и вернуться к использованию String.Split () для обработки его результатов.

0 голосов
/ 21 июня 2011

Вы можете делать такие вещи, как Linq и lambda, но я не думаю, что вы увидите положительную разницу в производительности, и это будет больше кода, чем если бы вы просто проанализировали это нормально.Я не собираюсь делать все это для вас, если вы действительно не нуждаетесь в помощи и действительно хотите сделать это таким образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...