我再次问这个问题,因为上次我问过它,它被错误地标记为重复。我这次将包含更多信息,这可能使我更容易理解我的需要(由于没有正确定义问题,这可能是我自己的错误。)
我正在尝试将通用类型的列表拆分为4个列表。为了简单和理解,我将在这个例子中使用一个整数列表,但这不应该有所作为。
我做了很多搜索,找到了多个答案,例如"Split List into Sublists with LINQ",using batch methods to split,我尝试了MoreLinq的Batch方法等等。这些建议适用于他们应该做的事情,但它们并不像我需要的那样工作。
如果我有一个包含以下元素的列表(1-25的整数):
[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]
然后我需要做的是制作4个列表,其中包含可变数量的元素,其中元素在同一列表中递增,而不是使用下一个元素跳转到下一个列表。
[ 1, 2, 3, 4, 5, 6, 7]
[ 8, 9, 10, 11, 12, 13, 14]
[15, 16, 17, 18, 19, 20, 21]
[20, 21, 22, 23, 24, 25]
当在链接的任何一个问题中使用解决方案时,以4“部分”作为参数,我得到这样的列表(这是元素跳转到下一个列表而不是列表的下一个元素的示例):
[1, 5, 9, 13, 17, 21, 25],
[2, 6, 10, 14, 18, 22, 26],
[3, 7, 11, 15, 19, 23, 27],
[4, 8, 12, 16, 20, 24]
或者这(与MoreLinq的批处理方法相同)
[ 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],
因此,第一个解决方案将列表拆分为4个列表,但是将元素放入错误的顺序。第二种解决方案按正确的顺序拆分列表,但不是正确的长度。在最后一个解决方案中,他获得了X个列表,每个列表中包含4个元素,其中我需要有4个列表,每个列表中包含X个元素。
答案 0 :(得分:8)
您可以使用以下扩展方法在所需数量的子列表上拆分列表,并在第一个子列表中包含其他项目:
public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
int rangeSize = source.Count / count;
int firstRangeSize = rangeSize + source.Count % count;
int index = 0;
yield return source.GetRange(index, firstRangeSize);
index += firstRangeSize;
while (index < source.Count)
{
yield return source.GetRange(index, rangeSize);
index += rangeSize;
}
}
给定输入
var list = Enumerable.Range(1, 25).ToList();
var result = list.Split(4);
结果是
[
[ 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 ]
]
更新:此解决方案为每个范围添加了额外的项目
public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
int rangeSize = source.Count / count;
int additionalItems = source.Count % count;
int index = 0;
while (index < source.Count)
{
int currentRangeSize = rangeSize + ((additionalItems > 0) ? 1 : 0);
yield return source.GetRange(index, currentRangeSize);
index += currentRangeSize;
additionalItems--;
}
}
答案 1 :(得分:2)
以下是基于IEnumerable<T>
的另一种解决方案。它具有以下特点:
batchCount
项,如果源可枚举小于批量大小,则会产生空列表。第一个示例针对List<T>
public static class BatchOperations
{
public static IEnumerable<List<T>> Batch<T>(this List<T> items, int batchCount)
{
int totalSize = items.Count;
int remain = totalSize % batchCount;
int skip = 0;
for (int i = 0; i < batchCount; i++)
{
int size = totalSize / batchCount + (i <= remain ? 1 : 0);
if (skip + size > items.Count) yield return new List<T>(0);
else yield return items.GetRange(skip, size);
skip += size;
}
}
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int batchCount)
{
int totalSize = items.Count();
int remain = totalSize%batchCount;
int skip = 0;
for (int i = 0; i < batchCount; i++)
{
int size = totalSize/batchCount + (i <= remain ? 1 : 0);
yield return items.Skip(skip).Take(size);
skip += size;
}
}
}
答案 2 :(得分:2)
谢尔文的回答显然是最好的,但为了完整性,如果你不想因某种原因制作副本列表,你可以使用这个解决方案(也许是因为你刚才{{ 1}}作为输入):
IEnumerable<T>
我想这会比使用Lists慢得多,但它会使用更少的内存。
答案 3 :(得分:1)
const int groupSize = 4;
var items = new []{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};
var currentGroupIndex=-1;
var step1 = items.Select(a =>{
if (++currentGroupIndex >= groupSize)
currentGroupIndex = 0;
return new {Group = currentGroupIndex, Value = a};
}).ToArray();
var step2 = step1.GroupBy(a => a.Group).Select(a => a.ToArray()).ToArray();
var group1 = step2[0].Select(a => a.Value).ToArray();
var group2 = step2[1].Select(a => a.Value).ToArray();
var group3 = step2[2].Select(a => a.Value).ToArray();
var group4 = step2[3].Select(a => a.Value).ToArray();
这样做首先引入一个计数器(currentGroupIndex
),它从零开始,并将为列表中的每个元素递增。当组大小达到时,索引将重置为零
变量step1
现在包含属于Group
和Value
属性的项目
然后在Group
语句中使用GroupBy
值。
答案 4 :(得分:0)
Take
&amp;我认为Skip
可能非常有用,但我个人喜欢使用Func
来做出这些选择,让方法更灵活。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Splitter
{
class Program
{
static void Main(string[] args)
{
List<int> numbers = Enumerable.Range(1, 25).ToList();
int groupCount = 4;
var lists = numbers.Groupem(groupCount, (e, i) =>
{
// In what group do i wanna have this element.
int divider = numbers.Count / groupCount;
int overflow = numbers.Count % divider;
int index = (i - overflow) / divider;
return index < 0 ? 0 : index;
});
Console.WriteLine("numbers: {0}", numbers.ShowContent());
Console.WriteLine("Parts");
foreach (IEnumerable<int> list in lists)
{
Console.WriteLine("{0}", list.ShowContent());
}
}
}
public static class EnumerableExtensions
{
private static List<T>[] CreateGroups<T>(int size)
{
List<T>[] groups = new List<T>[size];
for (int i = 0; i < groups.Length; i++)
{
groups[i] = new List<T>();
}
return groups;
}
public static void Each<T>(this IEnumerable<T> source, Action<T, int> action)
{
var i = 0;
foreach (var e in source) action(e, i++);
}
public static IEnumerable<IEnumerable<T>> Groupem<T>(this IEnumerable<T> source, int groupCount, Func<T, int, int> groupPicker, bool failOnOutOfBounds = true)
{
if (groupCount <= 0) throw new ArgumentOutOfRangeException("groupCount", "groupCount must be a integer greater than zero.");
List<T>[] groups = CreateGroups<T>(groupCount);
source.Each((element, index) =>
{
int groupIndex = groupPicker(element, index);
if (groupIndex < 0 || groupIndex >= groups.Length)
{
// When allowing some elements to fall out, set failOnOutOfBounds to false
if (failOnOutOfBounds)
{
throw new Exception("Some better exception than this");
}
}
else
{
groups[groupIndex].Add(element);
}
});
return groups;
}
public static string ShowContent<T>(this IEnumerable<T> list)
{
return "[" + string.Join(", ", list) + "]";
}
}
}
答案 5 :(得分:0)
这个怎么样,包括参数检查,使用空集。
完成两次通过,应该很快,我没有经过测试。
public static IList<Ilist<T>> Segment<T>(
this IEnumerable<T> source,
int segments)
{
if (segments < 1)
{
throw new ArgumentOutOfRangeException("segments");
}
var list = source.ToList();
var result = new IList<T>[segments];
// In case the source is empty.
if (!list.Any())
{
for (var i = 0; i < segments; i++)
{
result[i] = new T[0];
}
return result;
}
int remainder;
var segmentSize = Math.DivRem(list.Count, segments, out remainder);
var postion = 0;
var segment = 0;
while (segment < segments)
{
var count = segmentSize;
if (remainder > 0)
{
remainder--;
count++;
}
result[segment] = list.GetRange(position, count);
segment++;
position += count;
}
return result;
}
答案 6 :(得分:0)
这是一个优化的轻量级 O(N) 扩展方法解决方案
public static void Bifurcate<T>(this IEnumerable<T> _list, int amountOfListsOutputted, IList<IList<T>> outLists)
{
var list = _list;
var index = 0;
outLists = new List<IList<T>>(amountOfListsOutputted);
for (int i = 0; i < amountOfListsOutputted; i++)
{
outLists.Add(new List<T>());
}
foreach (var item in list)
{
outLists[index % amountOfListsOutputted].Add(item);
++index;
}
}
简单地使用它:
public static void Main()
{
var list = new List<int>(1000);
//Split into 2
list.Bifurcate(2, out var twoLists);
var splitOne = twoLists[0];
var splitTwo = twoLists[1];
// splitOne.Count == 500
// splitTwo.Count == 500
//Split into 3
list.Bifurcate(3, out var threeLists);
var _splitOne = twoLists[0];
var _splitTwo = twoLists[1];
var _splitThree = twoLists[2];
// _splitOne == 334
// _splitTwo = 333
// _splitThree == 333
}