Разделите коллекцию C# на равные части, сохраняя сортировку
Я пытаюсь разделить коллекцию на несколько коллекций, сохраняя при этом вид, который у меня есть в коллекции. Я попытался использовать следующий метод расширения, но он нарушает их неправильно. В принципе, если я должен был посмотреть на предметы в коллекции, порядок должен быть таким же по сравнению с разбитыми коллекциями. Вот код, который я использую, который не работает:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int i = 0;
var splits = from name in list
group name by i++ % parts into part
select part.AsEnumerable();
return splits;
}
- инт части = количество суб enumerables
8 ответов
мне пришлось использовать это, чтобы сравнить список объектов друг с другом в группах по 4... он сохранит предметы в том порядке, в каком они были у оригинала. Может быть расширен, чтобы сделать что-то другое, чем "список"
/// <summary>
/// Partition a list of elements into a smaller group of elements
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="totalPartitions"></param>
/// <returns></returns>
public static List<T>[] Partition<T>(List<T> list, int totalPartitions)
{
if (list == null)
throw new ArgumentNullException("list");
if (totalPartitions < 1)
throw new ArgumentOutOfRangeException("totalPartitions");
List<T>[] partitions = new List<T>[totalPartitions];
int maxSize = (int)Math.Ceiling(list.Count / (double)totalPartitions);
int k = 0;
for (int i = 0; i < partitions.Length; i++)
{
partitions[i] = new List<T>();
for (int j = k; j < k + maxSize; j++)
{
if (j >= list.Count)
break;
partitions[i].Add(list[j]);
}
k += maxSize;
}
return partitions;
}
немного более чистый подход LINQ, для этого довольно старого вопроса:
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int n)
{
var count = source.Count();
return source.Select((x, i) => new { value = x, index = i })
.GroupBy(x => x.index / (int)Math.Ceiling(count / (double)n))
.Select(x => x.Select(z => z.value));
}
библиотека MoreLINQ Джона Скита может сделать трюк для вас:
https://code.google.com/p/morelinq/source/browse/MoreLinq/Batch.cs
var items = list.Batch(parts); // gives you IEnumerable<IEnumerable<T>>
var items = list.Batch(parts, seq => seq.ToList()); // gives you IEnumerable<List<T>>
// etc...
еще пример:
public class Program
{
static void Main(string[] args)
{
var list = new List<int>();
for (int i = 1; i < 10000; i++)
{
list.Add(i);
}
var batched = list.Batch(681);
// will print 15. The 15th element has 465 items...
Console.WriteLine(batched.Count().ToString());
Console.WriteLine(batched.ElementAt(14).Count().ToString());
Console.WriteLine();
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
}
когда я сканировал содержимое пакетов, заказ был сохранен.
double partLength = list.Count() / (double)parts;
int i = 0;
var splits = from name in list
group name by Math.Floor((double)(i++ / partLength)) into part
select part;
Как я понимаю, вы хотите разбить перечисляемое на несколько частей с одинаковым размером и без нарушения порядка ваших элементов. Похоже, единственный выбор-сначала получить длину вашего ввода enumerable, поэтому вам понадобится по крайней мере две итерации через enumerable.
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int nGroups = (int)Math.Ceiling(list.Count() / (double)parts);
var groups = Enumerable.Range(0, nGroups);
return groups.Select(g => list.Skip(g * parts).Take(parts));
}
Это будет делать именно так, как просил. Он также будет обслуживать неравномерные группировки, т. е. 27 элементов в 10 группах дадут 7 групп по три и 3 группы по два
public static IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(this IEnumerable<T> list, int parts)
{
if (list.Count() == 0) return Enumerable.Empty<IEnumerable<T>>();
var toreturn = new List<IEnumerable<T>>();
var splitFactor = Decimal.Divide((decimal)list.Count(), parts);
int currentIndex = 0;
for (var i = 0; i < parts; i++)
{
var toTake = Convert.ToInt32(
i == 0 ? Math.Ceiling(splitFactor) : (
(Decimal.Compare(Decimal.Divide(Convert.ToDecimal(currentIndex), Convert.ToDecimal(i)), splitFactor) > 0) ?
Math.Floor(splitFactor) : Math.Ceiling(splitFactor)));
toreturn.Add(list.Skip(currentIndex).Take(toTake));
currentIndex += toTake;
}
return toreturn;
}
для демонстрационных целей
[TestMethod]
public void splitlist()
{
var list = new decimal[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 };
var splitlists = list.SplitMaintainingOrder(10);
int i = 1;
foreach (var group in splitlists)
{
Console.WriteLine("Group {0} elements {1}", i++, String.Join(",", group));
}
}
вышеуказанная демонстрация дает
Test Name: splitlist
Test Outcome: Passed
Result StandardOutput:
Group 1 elements 1,2,3
Group 2 elements 4,5
Group 3 elements 6,7,8
Group 4 elements 9,10,11
Group 5 elements 12,13
Group 6 elements 14,15,16
Group 7 elements 17,18,19
Group 8 elements 20,21
Group 9 elements 22,23,24
Group 10 elements 25,26,27
чтобы разделить общий список на равные куски, используйте ниже общий метод
private IEnumerable<IEnumerable<T>> SplitMaintainingOrder<T>(IEnumerable<T> list, int columnCount)
{
var elementsCount = list.Count();
int rowCount = elementsCount / columnCount;
int noOfCells = elementsCount % columnCount;
int finalRowCount = rowCount;
if (noOfCells > 0)
{
finalRowCount++;
}
var toreturn = new List<IEnumerable<T>>();
var pushto = 0;
for (int j = 0; j < columnCount; j++)
{
var start = j;
int i = 0;
var end = i;
for (i = 0; i < finalRowCount; i++)
{
if ((i < rowCount) || ((i == rowCount) && (j < noOfCells)))
{
start = j;
end = i;
}
}
toreturn.Add(list.Skip(pushto).Take(end + 1));
pushto += end + 1;
}
return toreturn;
}
List<int> recordNumbers = new List<int>() { 1, 2, 3, 4, 5, 6,7,8,9,10,11};
var splitedItems = SplitMaintainingOrder<int>(recordNumbers , 4);
Output will be:
List 1 : 1,2,3
List 2 : 4,5,6
List 3 : 7,8,9
List 4 : 10,11
~счастливые кодирования..