2015-07-08 2 views
1

У меня есть набор строк, как следующее:Идентификации и группирования схожих элементов в коллекции строк

List<string> codes = new List<string> 
{ 
    "44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10" 
}; 

Каждая строка состоит из двух компонентов, разделенных полная остановка - код префикса и субкод , Некоторые строки не имеют подкодов.

Я хочу, чтобы иметь возможность объединить строки, чьи префиксы являются одинаковыми и выводить их следующим образом с другими кодами также:

44 (01,02,03,04,05,06,07, 08) 46,47.10

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

+0

Вы хотите получить решение 'linq', или вы не будете использовать« llinq »? –

+0

Независимо от того, что делает работа и является читаемым. –

+2

Вы можете начать с сортировки списка! По умолчанию сопоставление сортирует список в [лексикографическом порядке] (https://en.wikipedia.org/wiki/Lexicographic_order). Затем сверните по списку и выберите префикс. Пока он остается тем же, вы можете добавить суффикс в свой «подписок». – MrPaulch

ответ

5

Вы можете сделать:

var query = codes.Select(c => 
    new 
    { 
     SplitArray = c.Split('.'), //to avoid multiple split 
     Value = c 
    }) 
    .Select(c => new 
    { 
     Prefix = c.SplitArray.First(), //you can avoid multiple split if you split first and use it later 
     PostFix = c.SplitArray.Last(), 
     Value = c.Value, 
    }) 
    .GroupBy(r => r.Prefix) 
    .Select(grp => new 
    { 
     Key = grp.Key, 
     Items = grp.Count() > 1 ? String.Join(",", grp.Select(t => t.PostFix)) : "", 
     Value = grp.First().Value, 
    }); 

Вот как это работает:

  • Разделить каждый элемент в списке на разделителе и заполнить анонимный тип Prefix, Postfix и оригинальный value
  • Позже группа по Prefix
  • после этого выберите значения и значения после починки с помощью string.Join

Для вывода:

foreach (var item in query) 
{ 
    if(String.IsNullOrWhiteSpace(item.Items)) 
     Console.WriteLine(item.Value); 
    else 
     Console.WriteLine("{0}({1})", item.Key, item.Items); 
} 

выход будет:

44(01,02,03,04,05,06,07,08) 
46 
47.10 
+1

Отличный ответ, я проверил выход в Fiddle. У меня действительно был отдельный запрос об устранении любых ведущих нулей в субкодах в [CodeReview StackExchange] (http://codereview.stackexchange.com/questions/96252/basic-string-manipulation-trim-leading-zeroes-from-second-component -f-a-strin) и задавался вопросом, было ли у вас более элегантное решение для того, что я представил? –

+0

@CiaranGallagher, спасибо, просто ответил на ваш запрос в CodeReview. – Habib

1

Изложенная идея:

  • Использование Dictionary<string, List<string>> для сбора Вашего результата

  • в цикле по вашему списку, используйте string.split() .. первый элемент будет ваш словарь ключа ... создать новую List<string> там, если ключа еще нет

  • если результат разделения имеет второй элемент, добавьте его в список

  • использовать второй цикл для форматирования, что словарь в свой выходной строке

Конечно, LINQ можно также, например,

List<string> codes = new List<string>() { 
    "44.01", "44.05", "47", "42.02", "44.03" }; 

var result = string.Join(",", 
    codes.OrderBy(x => x) 
    .Select(x => x.Split('.')) 
    .GroupBy(x => x[0]) 
    .Select((x) => 
    { 
     if (x.Count() == 0) return x.Key; 
     else if (x.Count() == 1) return string.Join(".", x.First()); 
     else return x.Key + "(" + string.Join(",", x.Select(e => e[1]).ToArray()) + ")"; 
    }).ToArray()); 

Gotta love linq ... haha ​​... Я думаю, что это монстр.

0

Общая идея, но я уверен, что замена Substring звонков с Regex будет намного лучше, а

List<string> newCodes = new List<string>() 
foreach (string sub1 in codes.Select(item => item.Substring(0,2)).Distinct) 
{ 
    StringBuilder code = new StringBuilder(); 
    code.Append("sub1("); 
    foreach (string sub2 in codes.Where(item => item.Substring(0,2) == sub1).Select(item => item.Substring(2)) 
     code.Append(sub2 + ","); 
    code.Append(")"); 
    newCodes.Add(code.ToString()); 
} 
0

Вы могли бы пойти пару способов ... Я мог видеть, что вы делаете Dictionary<string,List<string>> так, чтобы у вас могла быть «44» карта в список «.01», «.02», «.03» и т. Д.} потребует от вас обработки кодов перед их добавлением в этот список (т. е. разделяя две части кода и обрабатывая случай, когда имеется только одна часть).

Или вы можете поместить их в SortedSet и предоставить свой собственный компаратор, который знает, что это коды и как их сортировать (по крайней мере, это было бы более надежно, чем группировка в алфавитном порядке). Итерация по этому SortedSet по-прежнему требует специальной логики, поэтому, возможно, предпочтительнее вариант «Словарь для списка» выше.

В любом случае вам все равно придется обрабатывать специальный случай «46», где в коде отсутствует второй элемент. В примере словаря вы бы ввели String.Empty в список? Не уверен, что вы будете выводить, если у вас есть список {«46», «46.1»} - вы бы указали «46 (null, 1)» или ... «46 (0,1)» ... или «46 (, 1)» или «46 (1)»?

5

Попробуйте это: -

var result = codes.Select(x => new { SplitArr = x.Split('.'), OriginalValue = x }) 
        .GroupBy(x => x.SplitArr[0]) 
        .Select(x => new 
        { 
         Prefix= x.Key, 
         subCode = x.Count() > 1 ? 
          String.Join(",", x.Select(z => z.SplitArray[1])) : "", 
         OriginalValue = x.First().OriginalValue 
        }); 

Вы можете распечатать желаемый результат, как это: -

foreach (var item in result) 
{ 
    Console.Write("{0}({1}),",item.Prefix,item.subCode); 
} 

Working Fiddle.

+1

Незначительная ошибка, эта будет возвращать '47 (10)' для последнего значения, а не '47.10' по желанию OP – Habib

+0

@Habib - Спасибо, просто увидели, это не опечатка от OP? Почему последний - это особый случай ..? –

+0

Я предполагаю, что если счетчик является точным, чем OP хочет исходное значение, это может быть просто другой случай использования. – Habib

1

Вы можете сделать все это в одном умный LINQ:

var grouped = codes.Select(x => x.Split('.')) 
        .Select(x => new 
        { 
         Prefix = int.Parse(x[0]), 
         Subcode = x.Length > 1 ? int.Parse(x[1]) : (int?)null 
        }) 
        .GroupBy(k => k.Prefix) 
        .Select(g => new 
        { 
         Prefix = g.Key, 
         Subcodes = g.Where(s => s.Subcode.HasValue).Select(s => s.Subcode) 
        }) 
        .Select(x => 
         x.Prefix + 
         (x.Subcodes.Count() == 1 ? string.Format(".{0}", x.Subcodes.First()) : 
         x.Subcodes.Count() > 1 ? string.Format("({0})", string.Join(",", x.Subcodes)) 
               : string.Empty) 
        ).ToArray(); 
  1. Сначала он расщепляется Code и Subcode
  2. Group вами Code и получить все Subcode S как коллекция
  3. Выберите его в соответствующем формате

Глядя на эту проблему, я думаю, что вы должен быть остановлен как раз перед последним Select и дать представление данных в другой части/методе вашего приложения.

+0

Незначительная ошибка, эта будет возвращена '47' за последний элемент, а не' 47.10', как задал OP – Habib

+0

@Habib, исправленный, более сложный, чтобы он появился – RMalke

1

старинке:

List<string> codes = new List<string>() {"44.01", "44.05", "47", "42.02", "44.03" }; 
string output="" 
for (int i=0;i<list.count;i++) 
{ 
    string [] items= (codes[i]+"..").split('.') ; 
    int pos1=output.IndexOf(","+items[0]+"(") ; 
    if (pos1<0) output+=","+items[0]+"("+items[1]+")" ; // first occurence of code : add it 
    else 
    { // Code already inserted : find the insert point 
    int pos2=output.Substring(pos1).IndexOf(')') ; 
    output=output.Substring(0,pos2)+","+items[1]+output.Substring(pos2) ; 
    } 
} 
if (output.Length>0) output=output.Substring(1).replace("()","") ; 
1

Это будет работать, в том числе правильных форматов нет субкодов, один подкод, несколько субкодов. Он также не предполагает, что префикс или подкоды являются числовыми, поэтому он оставляет ведущие нули как есть. Ваш вопрос не показал, что делать, если у вас есть префикс без субкода И тот же префикс с субкодом, поэтому он может не работать в этом случае края (44,44.01). У меня это так, что он игнорирует префикс без субкода в этом случае.

List<string> codes = new List<string> 
{ 
    "44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10" 
}; 
var result=codes.Select(x => (x+".").Split('.')) 
        .Select(x => new 
        { 
         Prefix = x[0], 
         Subcode = x[1] 
        }) 
        .GroupBy(k => k.Prefix) 
        .Select(g => new 
        { 
         Prefix = g.Key, 
         Subcodes = g.Where(s => s.Subcode!="").Select(s => s.Subcode) 
        }) 
        .Select(x => 
         x.Prefix + 
         (x.Subcodes.Count() == 0 ? string.Empty : 
         string.Format(x.Subcodes.Count()>1?"({0})":".{0}", 
         string.Join(",", x.Subcodes))) 
        ).ToArray(); 
Смежные вопросы