2011-06-02 2 views
0

У меня есть файл CSV с записями, которые необходимо отсортировать, а затем сгруппировать в партии произвольного размера (например, 300 максимальных записей за партию). Каждая партия может иметь менее 300 записей, поскольку содержимое каждой партии должно быть однородным (на основе содержимого пары разных столбцов).Пакет с несколькими группами GroupBy

Мой LINQ заявление, вдохновленный этим ответом на batching with LINQ, выглядит следующим образом:

var query = (from line in EbrRecords 
      let EbrData = line.Split('\t') 
      let Location = EbrData[7] 
      let RepName = EbrData[4] 
      let AccountID = EbrData[0] 
      orderby Location, RepName, AccountID). 
      Select((data, index) => new { 
       Record = new EbrRecord(
       AccountID = EbrData[0], 
       AccountName = EbrData[1], 
       MBSegment = EbrData[2], 
       RepName = EbrData[4], 
       Location = EbrData[7], 
       TsrLocation = EbrData[8] 
       ) 
       , 
       Index = index} 
       ).GroupBy(x => new {x.Record.Location, x.Record.RepName, batch = x.Index/100});  

"/ 100" дает мне произвольный размер ведра. Другие элементы groupby предназначены для достижения гомогенности между партиями. Я подозреваю, что это почти то, что я хочу, но он дает мне следующую ошибку компилятора: A query body must end with a select clause or a group clause. Я понимаю, почему я получаю ошибку, но в целом я не уверен, как исправить этот запрос. Как это сделать?

UPDATE Я почти достиг того, что я после, со следующим:

List<EbrRecord> input = new List<EbrRecord> { 
    new EbrRecord {Name = "Brent",Age = 20,ID = "A"}, 
    new EbrRecord {Name = "Amy",Age = 20,ID = "B"}, 
    new EbrRecord {Name = "Gabe",Age = 23,ID = "B"}, 
    new EbrRecord {Name = "Noah",Age = 27,ID = "B"}, 
    new EbrRecord {Name = "Alex",Age = 27,ID = "B"}, 
    new EbrRecord {Name = "Stormi",Age = 27,ID = "B"}, 
    new EbrRecord {Name = "Roger",Age = 27,ID = "B"}, 
    new EbrRecord {Name = "Jen",Age = 27,ID = "B"}, 
    new EbrRecord {Name = "Adrian",Age = 28,ID = "B"}, 
    new EbrRecord {Name = "Cory",Age = 29,ID = "C"}, 
    new EbrRecord {Name = "Bob",Age = 29,ID = "C"}, 
    new EbrRecord {Name = "George",Age = 29,ID = "C"}, 
    }; 

//look how tiny this query is, and it is very nearly the result I want!!! 
int i = 0; 
var result = from q in input 
       orderby q.Age, q.ID 
       group q by new { q.ID, batch = i++/3 }; 

foreach (var agroup in result) 
{ 
    Debug.WriteLine("ID:" + agroup.Key); 
    foreach (var record in agroup) 
    { 
     Debug.WriteLine(" Name:" + record.Name); 
    } 
} 

Хитрость здесь в обход выберите «позиции индекса» overlaod, с помощью переменной закрытия (int i в Это дело). Полученные результаты следующие:

ID:{ ID = A, batch = 0 } 
Name:Brent 
ID:{ ID = B, batch = 0 } 
Name:Amy 
Name:Gabe 
ID:{ ID = B, batch = 1 } 
Name:Noah 
Name:Alex 
Name:Stormi 
ID:{ ID = B, batch = 2 } 
Name:Roger 
Name:Jen 
Name:Adrian 
ID:{ ID = C, batch = 3 } 
Name:Cory 
Name:Bob 
Name:George 

Несмотря на то, что этот ответ является приемлемым, это всего лишь небольшая часть, отличная от идеального результата. Должно быть, первое появление «партии« B »должно иметь в себе 3 (Эми, Гейб, Ной) - не две (Эми, Гейб). Это связано с тем, что позиция индекса не сбрасывается при идентификации каждой группы. Кто-нибудь знает, как сбросить мою пользовательскую позицию индекса для каждой группы?

ОБНОВЛЕНИЕ 2 Возможно, я нашел ответ. Во-первых, сделать дополнительную функцию:

public static bool BatchGroup(string ID, ref string priorID) 
    { 
     if (priorID != ID) 
     { 
      priorID = ID; 
      return true; 
     } 
     return false; 
    } 

Во-вторых, обновить запрос LINQ, как это:

int i = 0; 
string priorID = null; 
var result = from q in input 
       orderby q.Age, q.ID 
      group q by new { q.ID, batch = (BatchGroup(q.ID, ref priorID) ? i=0 : ++i)/3 }; 

Теперь он делает то, что я хочу. Мне просто жаль, что мне не нужна эта отдельная функция!

ответ

1
orderby Location, RepName, AccountID 

Там должно быть выберите пункт после того, как выше, как показано в ответ StriplingWarrior в. Запросы Linq Comprehension должны заканчиваться выбором или группой.


К сожалению, есть логический дефект ... Предположим, у меня есть 50 счетов в первой группе и 100 счетов во второй группе с партии размером 100. Исходный код будет производить 3 порции размером 50 , а не 2 партии по 50, 100.

Вот один из способов исправить это.

IEnumerable<IGrouping<int, EbrRecord>> query = ... 

    orderby Location, RepName, AccountID 
    select new EbrRecord(
    AccountID = EbrData[0], 
    AccountName = EbrData[1], 
    MBSegment = EbrData[2], 
    RepName = EbrData[4], 
    Location = EbrData[7], 
    TsrLocation = EbrData[8]) into x 
    group x by new {Location = x.Location, RepName = x.RepName} into g 
    from g2 in g.Select((data, index) => new Record = data, Index = index }) 
       .GroupBy(y => y.Index/100, y => y.Record) 
    select g2; 


List<List<EbrRecord>> result = query.Select(g => g.ToList()).ToList(); 

Также обратите внимание, что использование GroupBy к партии очень медленно из-за избыточные итерации. Вы можете написать цикл for, который сделает это за один проход по упорядоченному набору, и этот цикл будет работать намного быстрее, чем LinqToObjects.

+0

Мой intellisense и компилятор отказывается разрешить мне поместить «группу» после «выбрать новый», если я не переключусь на точечную нотацию. –

+0

«В x» важно. –

+0

Исправлено много неловких опечаток. Теперь я закончил (работает ли это или нет). –

2

Это работает?

var query = (from line in EbrRecords 
     let EbrData = line.Split('\t') 
     let Location = EbrData[7] 
     let RepName = EbrData[4] 
     let AccountID = EbrData[0] 
     orderby Location, RepName, AccountID 
     select new EbrRecord(
       AccountID = EbrData[0], 
       AccountName = EbrData[1], 
       MBSegment = EbrData[2], 
       RepName = EbrData[4], 
       Location = EbrData[7], 
       TsrLocation = EbrData[8]) 
     ).Select((data, index) => new 
     { 
      Record = data, 
      Index = index 
     }) 
     .GroupBy(x => new {x.Record.Location, x.Record.RepName, batch = x.Index/100}, 
      x => x.Record); 
+0

Что я ожидаю, это список списков EbrRecord (список списков). Но приведенное выше дает мне список анонимного типа, который содержит только Location, RepName и пакет. Мне интересно, что сообщение, которое я связал, на самом деле делает то, что я думал или надеялся. –

+0

@Brent: GroupBy создаст 'IEnumerable'' IGrouping ', каждый из которых имеет« ключ »с именем« Location »,« RepName »и« пакет », но также является« IEnumerable », содержащим выбранные значения. Если вы используете перегрузку в моем обновленном ответе, вы должны иметь «IEnumerable >». Тем не менее, я думаю, что вполне вероятно, что это не делает то, что вы надеялись. Обязательно прочитайте ответ Дэвида Б. Он делает отличные очки. – StriplingWarrior

Смежные вопросы