我正在尝试将列表拆分为一系列较小的列表。
我的问题:我拆分列表的功能并没有将它们拆分成正确大小的列表。它应该将它们分成大小为30的列表,而是将它们分成大小为114的列表?
如何让我的功能将列表分成X个大小 30或更少的列表?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
如果我在大小为144的列表上使用该函数,则输出为:
指数:4,尺寸:120
指数:3,大小:114
指数:2,大小:114
指数:1,尺寸:114
指数:0,大小:114
答案 0 :(得分:315)
我建议使用此扩展方法将源列表按指定的块大小分块到子列表:
/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / chunkSize)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
}
例如,如果您按照每个块的5个项目清除18个项目的列表,它会为您提供4个子列表的列表,其中包含以下项目:5-5-5-3。
答案 1 :(得分:188)
public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30)
{
var list = new List<List<float[]>>();
for (int i=0; i < locations.Count; i+= nSize)
{
list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
}
return list;
}
通用版本:
public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30)
{
for (int i=0; i < locations.Count; i+= nSize)
{
yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i));
}
}
答案 2 :(得分:32)
怎么样:
while(locations.Any())
{
list.Add(locations.Take(nSize).ToList());
locations= locations.Skip(nSize).ToList();
}
答案 3 :(得分:11)
Serj-Tm解决方案很好,这也是通用版本作为列表的扩展方法(把它放到一个静态类中):
public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
List<List<T>> list = new List<List<T>>();
for (int i = 0; i < items.Count; i += sliceSize)
list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
return list;
}
答案 4 :(得分:7)
我发现接受的答案(Serj-Tm)最强大,但我想建议一个通用版本。
public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
{
var list = new List<List<T>>();
for (int i = 0; i < locations.Count; i += nSize)
{
list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
}
return list;
}
答案 5 :(得分:6)
我有一个通用的方法,可以采用任何类型包括浮点数,并且它已经过单元测试,希望它有所帮助:
/// <summary>
/// Breaks the list into groups with each group containing no more than the specified group size
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="groupSize">Size of the group.</param>
/// <returns></returns>
public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
{
List<List<T>> result = new List<List<T>>();
// Quick and special scenario
if (values.Count() <= groupSize)
{
result.Add(values.ToList());
}
else
{
List<T> valueList = values.ToList();
int startIndex = 0;
int count = valueList.Count;
int elementCount = 0;
while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
{
elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
result.Add(valueList.GetRange(startIndex, elementCount));
startIndex += elementCount;
}
}
return result;
}
答案 6 :(得分:4)
Library MoreLinq有一个名为Batch
List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
foreach(var eachId in batch)
{
Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
}
counter++;
}
结果是
Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0
ids
被分为5个包含2个元素的块。
答案 7 :(得分:3)
虽然上面有很多答案可以完成这项任务,但它们都会在一个永无止境的序列(或一个非常长的序列)上失败。以下是完全在线实现,可确保最佳时间和内存复杂性。我们只对源枚举进行一次迭代,并使用yield return进行延迟求值。消费者可以在每次迭代时丢弃列表,使内存占用量等于列表w / batchSize
元素数量。
public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
using (var enumerator = enumerable.GetEnumerator())
{
List<T> list = null;
while (enumerator.MoveNext())
{
if (list == null)
{
list = new List<T> {enumerator.Current};
}
else if (list.Count < batchSize)
{
list.Add(enumerator.Current);
}
else
{
yield return list;
list = new List<T> {enumerator.Current};
}
}
if (list?.Count > 0)
{
yield return list;
}
}
}
编辑:刚才意识到OP要求将List<T>
分成更小的List<T>
,所以我对无限枚举的评论不适用于OP,但可能会帮助其他人来到这里。这些评论是对其他使用IEnumerable<T>
作为其功能输入的已发布解决方案的回应,但是可以多次枚举可枚举的源。
答案 8 :(得分:2)
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
return items.Select((item, index) => new { item, index })
.GroupBy(x => x.index / maxItems)
.Select(g => g.Select(x => x.item));
}
答案 9 :(得分:2)
while ($row = $wynik->fetch_assoc()) {
echo "<tr>";
echo "<td rowspan='8'>".$row['date']."</td>";
echo "<td rowspan='8'>".$row['shift']."</td>";
echo "<td>".$row['stanowisko_1']."</td>";
echo "<td>".$row['a_1']."</td>";
echo "<td>".$row['a_2']."</td>";
echo "<td>".$row['a_3']."</td>";
echo "<td>".$row['a_4']."</td>";
echo "<td>".$row['stanowisko_2']."</td>";
echo "<td>".$row['b_1']."</td>";
echo "<td>".$row['b_2']."</td>";
echo "<td>".$row['b_3']."</td>";
echo "<td>".$row['b_4']."</td>";
echo "</tr>";
答案 10 :(得分:2)
在最后的mhand非常有用的评论后添加
虽然大多数解决方案都可行,但我认为它们效率不高。假设你只想要前几个块的前几个项目。那么你就不想迭代序列中的所有(数百个)项目。
以下将最多列举两次:一次为Take,一次为Skip。它不会枚举任何超出您使用的元素:
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
(this IEnumerable<TSource> source, int chunkSize)
{
while (source.Any()) // while there are elements left
{ // still something to chunk:
yield return source.Take(chunkSize); // return a chunk of chunkSize
source = source.Skip(chunkSize); // skip the returned chunk
}
}
假设您将源划分为chunkSize
的块。您只枚举前N个块。从每个枚举的块中,您只能枚举前M个元素。
While(source.Any())
{
...
}
Any将获取Enumerator,执行1 MoveNext()并在Disposing Enumerator之后返回返回的值。这将完成N次
yield return source.Take(chunkSize);
根据reference source,这将做类似的事情:
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
return TakeIterator<TSource>(source, count);
}
static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
foreach (TSource element in source)
{
yield return element;
if (--count == 0) break;
}
}
在你开始枚举获取的Chunk之前,这并没有做很多事情。如果您获取多个块,但决定不对第一个块进行枚举,则不会执行foreach,因为调试器会向您显示。
如果您决定获取第一个块的前M个元素,那么yield yield将执行恰好M次。这意味着:
在第一个块返回后,我们跳过第一个Chunk:
source = source.Skip(chunkSize);
再次:我们将reference source查看skipiterator
static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (count > 0 && e.MoveNext()) count--;
if (count <= 0)
{
while (e.MoveNext()) yield return e.Current;
}
}
}
如您所见,SkipIterator
为Chunk中的每个元素调用MoveNext()
一次。 它不会调用Current
。
所以每个Chunk我们看到以下内容已经完成:
Take():
如果内容被枚举:GetEnumerator(),一个MoveNext和一个Current per枚举项,Dispose enumerator;
Skip():对于每个枚举的块(不是块的内容): GetEnumerator(),MoveNext()chunkSize次,没有当前!处理枚举器
如果你看一下枚举器会发生什么,你会发现有很多调用MoveNext(),并且只调用Current
来查看你实际决定访问的TSource项目。
如果您使用大小为chunkSize的N Chunks,则调用MoveNext()
如果您决定仅枚举每个获取的块的前M个元素,那么您需要为每个枚举的块调用MoveNext M次。
总数
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
因此,如果您决定枚举所有块的所有元素:
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
MoveNext是否需要大量工作,取决于源序列的类型。对于列表和数组,它是一个简单的索引增量,可能超出范围检查。
但是如果您的IEnumerable是数据库查询的结果,请确保您的计算机上的数据确实已实现,否则数据将被多次提取。 DbContext和Dapper会在访问之前将数据正确地传输到本地进程。如果多次枚举相同的序列,则不会多次提取。 Dapper返回一个List对象,DbContext会记住数据已经被提取。
在开始划分块中的项目之前,依赖于您的存储库是否明智地调用AsEnumerable()或ToLists()
答案 11 :(得分:1)
这个怎么样?这个想法是只使用一个循环。而且,谁知道呢,也许您在代码中仅使用IList实现,而又不想转换为List。
private IEnumerable<IList<T>> SplitList<T>(IList<T> list, int totalChunks)
{
IList<T> auxList = new List<T>();
int totalItems = list.Count();
if (totalChunks <= 0)
{
yield return auxList;
}
else
{
for (int i = 0; i < totalItems; i++)
{
auxList.Add(list[i]);
if ((i + 1) % totalChunks == 0)
{
yield return auxList;
auxList = new List<T>();
}
else if (i == totalItems - 1)
{
yield return auxList;
}
}
}
}
答案 12 :(得分:0)
再一个
public static IList<IList<T>> SplitList<T>(this IList<T> list, int chunkSize)
{
var chunks = new List<IList<T>>();
List<T> chunk = null;
for (var i = 0; i < list.Count; i++)
{
if (i % chunkSize == 0)
{
chunk = new List<T>(chunkSize);
chunks.Add(chunk);
}
chunk.Add(list[i]);
}
return chunks;
}
答案 13 :(得分:0)
var str : String = "#tweak #wow #gaming"
if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive) {
regex.matches(in: str, options: [], range: NSRange(location: 0, length: str.utf8.count)).map {
print(str.substring(with: $0.range))
}
}
答案 14 :(得分:0)
List<int> orginalList =new List<int>(){1,2,3,4,5,6,7,8,9,10,12};
Dictionary<int,List<int>> dic = new Dictionary <int,List<int>> ();
int batchcount = orginalList.Count/2; //To List into two 2 parts if you
want three give three
List<int> lst = new List<int>();
for (int i=0;i<orginalList.Count; i++)
{
lst.Add(orginalList[i]);
if (i % batchCount == 0 && i!=0)
{
Dic.Add(threadId, lst);
lst = new List<int>();**strong text**
threadId++;
}
}
if(lst.Count>0)
Dic.Add(threadId, lst); //in case if any dayleft
foreach(int BatchId in Dic.Keys)
{
Console.Writeline("BatchId:"+BatchId);
Console.Writeline('Batch Count:"+Dic[BatchId].Count);
}
答案 15 :(得分:0)
我也遇到了同样的需求,并且我结合使用Linq的 Skip()和 Take()方法。我将到目前为止的迭代次数乘以我得到的数值,然后得出要跳过的项目数,然后进入下一组。
var categories = Properties.Settings.Default.MovementStatsCategories;
var items = summariesWithinYear
.Select(s => s.sku).Distinct().ToList();
//need to run by chunks of 10,000
var count = items.Count;
var counter = 0;
var numToTake = 10000;
while (count > 0)
{
var itemsChunk = items.Skip(numToTake * counter).Take(numToTake).ToList();
counter += 1;
MovementHistoryUtilities.RecordMovementHistoryStatsBulk(itemsChunk, categories, nLogger);
count -= numToTake;
}
答案 16 :(得分:0)
基于Dimitry Pavlov answere,我将删除.ToList()
。并且还要避免匿名类。
相反,我喜欢使用不需要堆内存分配的结构。 (ValueTuple
也可以做。)
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (chunkSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(chunkSize), chunkSize, "The argument must be greater than zero.");
}
return source
.Select((x, i) => new ChunkedValue<TSource>(x, i / chunkSize))
.GroupBy(cv => cv.ChunkIndex)
.Select(g => g.Select(cv => cv.Value));
}
[StructLayout(LayoutKind.Auto)]
[DebuggerDisplay("{" + nameof(ChunkedValue<T>.ChunkIndex) + "}: {" + nameof(ChunkedValue<T>.Value) + "}")]
private struct ChunkedValue<T>
{
public ChunkedValue(T value, int chunkIndex)
{
this.ChunkIndex = chunkIndex;
this.Value = value;
}
public int ChunkIndex { get; }
public T Value { get; }
}
可以像下面这样使用,它仅对集合进行一次迭代,然后 也不会分配任何重要的内存。
int chunkSize = 30;
foreach (var chunk in collection.ChunkBy(chunkSize))
{
foreach (var item in chunk)
{
// your code for item here.
}
}
如果实际上需要一个具体的列表,那么我会这样做:
int chunkSize = 30;
var chunkList = new List<List<T>>();
foreach (var chunk in collection.ChunkBy(chunkSize))
{
// create a list with the correct capacity to be able to contain one chunk
// to avoid the resizing (additional memory allocation and memory copy) within the List<T>.
var list = new List<T>(chunkSize);
list.AddRange(chunk);
chunkList.Add(list);
}
答案 17 :(得分:0)
基于 Serj-Tm 接受的答案的更通用的版本。
public static IEnumerable<IEnumerable<T>> Split<T>(IEnumerable<T> source, int size = 30)
{
var count = source.Count();
for (int i = 0; i < count; i += size)
{
yield return source
.Skip(Math.Min(size, count - i))
.Take(size);
}
}
答案 18 :(得分:0)
从 .NET 6.0 开始,您可以使用 LINQ 扩展 Chunk<T>()
将枚举拆分为块。 Docs
var chars = new List<char>() { 'h', 'e', 'l', 'l', 'o', 'w','o','r' ,'l','d' };
foreach (var batch in chars.Chunk(2))
{
foreach (var ch in batch)
{
// iterates 2 letters at a time
}
}
答案 19 :(得分:-1)
如果你想用条件而不是固定数字来分割它:
///<summary>
/// splits a list based on a condition (similar to the split function for strings)
///</summary>
public static IEnumerable<List<T>> Split<T>(this IEnumerable<T> src, Func<T, bool> pred)
{
var list = new List<T>();
foreach(T item in src)
{
if(pred(item))
{
if(list != null && list.Count > 0)
yield return list;
list = new List<T>();
}
else
{
list.Add(item);
}
}
}