Пакет с несколькими GroupBy - PullRequest
0 голосов
/ 02 июня 2011

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

Мое утверждение LINQ, вдохновленное этим ответом на пакетирование с помощью 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" дает мне произвольный размер корзины. Другие элементы группы предназначены для достижения однородности между партиями. Я подозреваю, что это почти то, что я хочу, но это дает мне следующую ошибку компилятора: A query body must end with a select clause or a group clause. Я понимаю, почему получаю ошибку, но в целом я не уверен, как исправить этот запрос. Как это будет сделано?

ОБНОВЛЕНИЕ Я почти достиг того, что хочу, со следующим:

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

Хитрость в том, чтобы обойти выбранную «позицию индекса» с помощью закрывающей переменной (в данном случае 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 };

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

Ответы [ 2 ]

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

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

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);
1 голос
/ 02 июня 2011
orderby Location, RepName, AccountID

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


К сожалению, есть логический дефект ... Предположим, у меня 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.

...