Разделить список <T> на основе длины элемента

2

У меня есть список Message вместе с длиной их элемента в качестве size, this.Size = JsonConvert.SerializeObject(Data).Length; ,

public class Data
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Message
{
    public Data Data { get; set; }
    public int Size { get; set; }

    public Message(Data Data)
    {
        this.Data = Data;
        this.Size = JsonConvert.SerializeObject(Data).Length;
    }
}

Теперь у меня есть набор данных ниже, и я хотел бы разбить список на несколько списков, и каждый список должен быть только 50 байтов (например).

Примечание - 50 в качестве примера, мой фактический размер составляет 1 МБ, и ни одно отдельное сообщение не превышает 1 МБ.

Я попробовал ниже, но как рассчитать общий размер и перейти к группе по chunksize,

.GroupBy(x => x.ItemSize / chunkSize)

ИЛИ другой путь?

var messages = new List<Message>
{
    new Message(new Data{ Id=100, Name="N100"}),
    new Message(new Data{ Id=1100, Name="N1100"}),
    new Message(new Data{ Id=11100, Name="N11100"}),
    new Message(new Data{ Id=111100, Name="N111100"}),
    new Message(new Data{ Id=1111100, Name="N1111100"})
};

int chunkSize = 50;

var X = messages
    .Select(x => new { ItemSize = x.Size, Value = x })
    .GroupBy(x => x.ItemSize / chunkSize)
    .Select(x => x.Select(v => v.Value).ToList())
    .ToList();
  • 0
    Что делать, если я не могу сделать это равномерно, что если одно сообщение больше 50 (размер), я думаю, вам нужно добавить больше информации
  • 0
    50 в качестве примера, мой фактический размер составляет 1 МБ, и ни одно отдельное сообщение не превышает 1 МБ
Показать ещё 4 комментария
Теги:

1 ответ

6
Лучший ответ

Это может сработать. Суть в том, что Lazily выполняет итерацию через IEnumerable<Message> и yields IEnumerable<List<Message>> (подсписок Message), где накопление больше размера chunk

public static IEnumerable<List<Message>> Split(this IEnumerable<Message> source, int chunk)
{

   var list = new List<Message>();
   var accum = 0;

   foreach (var message in source)
   {
      accum += message.Size;

      if (accum > chunk)
      {
         yield return list;
         list = new List<Message>();
         accum = message.Size;
      }
      list.Add(message);
   }

   // Return last result if any 
   if (list.Any()) yield return list;
}

использование

var sublists = messages.Split(50);

Обновить

Это полезный метод, поэтому я сделал его универсальным и более подходящим для библиотеки. Включает проверку работоспособности и выбрасывает, если size больше, чем chunk

public static IEnumerable<List<T>> Buffer<T>(this IEnumerable<T> source, int chunk, Func<T, long> selector)
{
   // safety first
   if (source == null) throw new ArgumentNullException(nameof(source));

   var list = new List<T>();
   long accum = 0;

   foreach (var item in source)
   {
      var size = selector(item);

      // sanity check
      if (size > chunk) throw new InvalidOperationException("Selector size cant be greater than chunk size");

      // Return chunk
      if ((accum += size) > chunk)
      {
         yield return list;
         list = new List<T>();
         accum = size;
      }

      list.Add(item); // always need to add the current
   }

   // Return any partial result
   if (list.Any()) yield return list;
}

использование

var results = messages.Buffer(50, x => x.Size)
  • 2
    Arrgghh! list.Clear(); это означает, что ранее возвращенные элементы в IEnumerable<List<Message>> также очищаются.
  • 1
    @ Энигмативность, ха-ха, хороший улов как всегда
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню