Создать вложенный список объектов из одного списка - PullRequest
1 голос
/ 03 октября 2019

У меня есть список элементов (не уверен, что они являются четным или нечетным числом элементов). То, что я хочу сделать, это собрать записи в паре из 5 (которая на самом деле является списком), создать другой список и вставить эти пары из 5 списков в этот новый список.

Спасибо

Я могу создать группу элементов, выполнив это

MyList
  .Zip(Enumerable.Range(0, MyList.Count()),
       (s, r) => new { 
          Group = r / 5, 
          Item = s })
  .GroupBy(i => i.Group, 
           g => g.Item)
  .ToList();

Но я хочу создать вложенный список.

Ответы [ 4 ]

0 голосов
/ 03 октября 2019

Почему не просто GroupBy?

 using System.Linq;

 ...

 int groupSize = 5;

 var result = MyList
   .Select((item, index) => new {
      item,
      index 
    })
   .GroupBy(pair => pair.index / groupSize, 
            pair => pair.item)
   .Select(group => group.ToList())
   .ToList(); 
0 голосов
/ 03 октября 2019

Если у вас есть коллекция предметов

var items = Enumerable.Range(1, 20);

И вы хотите взять, скажем, 5 за раз

var setSize = 5;

Вы можете перебирать коллекцию по индексу, ивозьмите эти 5 за один раз в виде списка и поместите все эти 5 списков в один внешний список

 Enumerable.Range(0, items.Count() - setSize).Select(x => items.Skip(x).Take(setSize).ToList()).ToList()

Результат (из интерактивной оболочки C #) выглядит как

List<List<int>>(15) { 
    List<int>(5) { 1, 2, 3, 4, 5 }, 
    List<int>(5) { 2, 3, 4, 5, 6 }, 
    List<int>(5) { 3, 4, 5, 6, 7 }, 
    List<int>(5) { 4, 5, 6, 7, 8 }, 
    List<int>(5) { 5, 6, 7, 8, 9 }, 
    List<int>(5) { 6, 7, 8, 9, 10 }, 
    List<int>(5) { 7, 8, 9, 10, 11 }, 
    List<int>(5) { 8, 9, 10, 11, 12 }, 
    List<int>(5) { 9, 10, 11, 12, 13 }, 
    List<int>(5) { 10, 11, 12, 13, 14 }, 
    List<int>(5) { 11, 12, 13, 14, 15 }, 
    List<int>(5) { 12, 13, 14, 15, 16 }, 
    List<int>(5) { 13, 14, 15, 16, 17 }, 
    List<int>(5) { 14, 15, 16, 17, 18 }, 
    List<int>(5) { 15, 16, 17, 18, 19 }
}

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

var items = Enumerable.Range(1, 11);

Вы хотите изменить начальный диапазон, используемый для индексации в вашей коллекции. Вместо того, чтобы брать по 5 за каждый индекс, он будет увеличивать индекс на 5 за каждую итерацию. Единственная сложная часть заключается в том, чтобы обрабатывать, когда коллекция делит количество элементов, которые вы хотите взять;Вы не хотите заканчивать с пустым списком в конце. То есть это неверно:

Enumerable.Range(0, items.Count() / setSize).Select( // don't do this

Тогда оператор выглядит так:

Enumerable.Range(0, ((items.Count() - 1) / setSize) + 1).Select(x => items.Skip(setSize * x).Take(setSize).ToList()).ToList();

Результат (из интерактивной оболочки C #) выглядит как

List<List<int>>(3) {
    List<int>(5) { 1, 2, 3, 4, 5 },
    List<int>(5) { 6, 7, 8, 9, 10 },
    List<int>(1) { 11 }
}
0 голосов
/ 03 октября 2019

Похоже, вы хотите партия элементов в пакетах по 5 штук в каждом. Пакет MoreLinq уже предлагает оператор Batch для этого:

var items=Enumerable.Range(0,17);
var batches=items.Batch(5);

foreach(var batch in batches)
{
   Console.WriteLine(String.Join(" - ",batch));
}

Это производит:

0 - 1 - 2 - 3 - 4
5 - 6 - 7 - 8 - 9
10 - 11 - 12 - 13 - 14
15 - 16

Это намного быстрее, чем группировкатак как он повторяет коллекцию только один раз.

В MoreLINQ есть и другие операторы, такие как Window , WindowLeft и WindowRight, которые создают скользящие окна значений. items.Window(5) выдаст:

0 - 1 - 2 - 3 - 4
1 - 2 - 3 - 4 - 5
...
11 - 12 - 13 - 14 - 15
12 - 13 - 14 - 15 - 16

Реализация

Реализация оператора достаточно проста, и вы можете просто скопировать ее в свой проект:

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(this IEnumerable<TSource> source, int size)
{
    return Batch(source, size, x => x);
}

public static IEnumerable<TResult> Batch<TSource, TResult>( IEnumerable<TSource> source, int size,
    Func<IEnumerable<TSource>, TResult> resultSelector)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
    if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));

    return _(); IEnumerable<TResult> _()
    {
        TSource[] bucket = null;
        var count = 0;

        foreach (var item in source)
        {
            if (bucket == null)
            {
                bucket = new TSource[size];
            }

            bucket[count++] = item;

            // The bucket is fully buffered before it's yielded
            if (count != size)
            {
                continue;
            }

            yield return resultSelector(bucket);

            bucket = null;
            count = 0;
        }

        // Return the last bucket with all remaining elements
        if (bucket != null && count > 0)
        {
            Array.Resize(ref bucket, count);
            yield return resultSelector(bucket);
        }
    }
}

Код использует массивы для эффективности. Если вы действительно хотите использовать изменяемые списки, вы можете изменить тип bucket на List<T>, например:

if (bucket == null)
{
   bucket = new List<TSource>(size); //IMPORTANT: set the capacity to avoid reallocations
}

bucket.Add(item);

...
0 голосов
/ 03 октября 2019

Не уверен, что я правильно понимаю вашу цель, но вы можете попробовать использовать словарь для этого:

MyList.Zip(Enumerable.Range(0, MyList.Count()),
  (s, r) => new { Group = r / 5, Item = s })
.GroupBy(i => i.Group, g => g.Item)
.ToDictionary(g => g.Key, g => g.ToList());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...