有什么方法可以将List<SomeObject>
分成几个单独的SomeObject
列表,使用项索引作为每个拆分的分隔符?
让我举例说明:
我有一个List<SomeObject>
我需要一个List<List<SomeObject>>
或List<SomeObject>[]
,这样每个结果列表都会包含一组原始列表中的3个项目(按顺序)。< / p>
例如:
原始列表:[a, g, e, w, p, s, q, f, x, y, i, m, c]
结果列表:[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
我还需要将结果列表大小作为此函数的参数。
答案 0 :(得分:342)
请尝试以下代码。
public static IList<IList<T>> Split<T>(IList<T> source)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 3)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
我们的想法是首先按索引对元素进行分组。除以3具有将它们分组为3的组的效果。然后将每个组转换为列表,并将IEnumerable
List
转换为List
List
s
答案 1 :(得分:300)
这个问题有点陈旧,但我刚写了这篇文章,我觉得它比其他提议的解决方案更优雅:
/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
while (source.Any())
{
yield return source.Take(chunksize);
source = source.Skip(chunksize);
}
}
答案 2 :(得分:93)
一般来说,CaseyB建议的方法运行正常,事实上如果你传入List<T>
它很难错,也许我会把它改为:
public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
var pos = 0;
while (source.Skip(pos).Any())
{
yield return source.Skip(pos).Take(chunksize);
pos += chunksize;
}
}
这将避免大规模的调用链。尽管如此,这种方法有一个普遍的缺陷。它实现了每个块的两个枚举,以突出显示尝试运行的问题:
foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
Console.WriteLine(item);
}
// wait forever
为了解决这个问题,我们可以尝试使用Cameron's方法,它以绚丽的色彩传递上述测试,因为它只会遍历枚举一次。
麻烦的是它有一个不同的缺陷,它实现了每个块中的每个项目,这种方法的麻烦在于你在内存上运行得很高。
为了说明尝试运行:
foreach (var item in Enumerable.Range(1, int.MaxValue)
.Select(x => x + new string('x', 100000))
.Clump(10000).Skip(100).First())
{
Console.Write('.');
}
// OutOfMemoryException
最后,任何实现都应该能够处理块的乱序迭代,例如:
Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]
许多高度优化的解决方案,例如我的第一个revision答案就失败了。在casperOne's optimized回答中可以看到同样的问题。
要解决所有这些问题,您可以使用以下内容:
namespace ChunkedEnumerator
{
public static class Extensions
{
class ChunkedEnumerable<T> : IEnumerable<T>
{
class ChildEnumerator : IEnumerator<T>
{
ChunkedEnumerable<T> parent;
int position;
bool done = false;
T current;
public ChildEnumerator(ChunkedEnumerable<T> parent)
{
this.parent = parent;
position = -1;
parent.wrapper.AddRef();
}
public T Current
{
get
{
if (position == -1 || done)
{
throw new InvalidOperationException();
}
return current;
}
}
public void Dispose()
{
if (!done)
{
done = true;
parent.wrapper.RemoveRef();
}
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
position++;
if (position + 1 > parent.chunkSize)
{
done = true;
}
if (!done)
{
done = !parent.wrapper.Get(position + parent.start, out current);
}
return !done;
}
public void Reset()
{
// per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
throw new NotSupportedException();
}
}
EnumeratorWrapper<T> wrapper;
int chunkSize;
int start;
public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
{
this.wrapper = wrapper;
this.chunkSize = chunkSize;
this.start = start;
}
public IEnumerator<T> GetEnumerator()
{
return new ChildEnumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class EnumeratorWrapper<T>
{
public EnumeratorWrapper (IEnumerable<T> source)
{
SourceEumerable = source;
}
IEnumerable<T> SourceEumerable {get; set;}
Enumeration currentEnumeration;
class Enumeration
{
public IEnumerator<T> Source { get; set; }
public int Position { get; set; }
public bool AtEnd { get; set; }
}
public bool Get(int pos, out T item)
{
if (currentEnumeration != null && currentEnumeration.Position > pos)
{
currentEnumeration.Source.Dispose();
currentEnumeration = null;
}
if (currentEnumeration == null)
{
currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
}
item = default(T);
if (currentEnumeration.AtEnd)
{
return false;
}
while(currentEnumeration.Position < pos)
{
currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
currentEnumeration.Position++;
if (currentEnumeration.AtEnd)
{
return false;
}
}
item = currentEnumeration.Source.Current;
return true;
}
int refs = 0;
// needed for dispose semantics
public void AddRef()
{
refs++;
}
public void RemoveRef()
{
refs--;
if (refs == 0 && currentEnumeration != null)
{
var copy = currentEnumeration;
currentEnumeration = null;
copy.Source.Dispose();
}
}
}
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
if (chunksize < 1) throw new InvalidOperationException();
var wrapper = new EnumeratorWrapper<T>(source);
int currentPos = 0;
T ignore;
try
{
wrapper.AddRef();
while (wrapper.Get(currentPos, out ignore))
{
yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
currentPos += chunksize;
}
}
finally
{
wrapper.RemoveRef();
}
}
}
class Program
{
static void Main(string[] args)
{
int i = 10;
foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
{
foreach (var n in group)
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
if (i-- == 0) break;
}
var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();
foreach (var idx in new [] {3,2,1})
{
Console.Write("idx " + idx + " ");
foreach (var n in stuffs[idx])
{
Console.Write(n);
Console.Write(" ");
}
Console.WriteLine();
}
/*
10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
*/
Console.ReadKey();
}
}
}
对于块的无序迭代,还可以进行一轮优化,这超出了范围。
您应该选择哪种方法?这完全取决于您试图解决的问题。如果你不关心第一个缺陷那么简单的答案是非常有吸引力的。
注意和大多数方法一样,这对于多线程是不安全的,如果你想让线程安全,那么你可能需要修改EnumeratorWrapper
。
答案 3 :(得分:64)
你可以使用大量使用Take
和Skip
的查询,但我认为这会在原始列表中添加太多迭代。
相反,我认为你应该创建一个你自己的迭代器,如下:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
然后,您可以调用它并启用LINQ,以便对结果序列执行其他操作。
根据Sam's answer,我觉得有一种更简单的方法可以做到这一点:
那就是说,这是另一个传递,我在IEnumerable<T>
的IEnumerator<T>
扩展方法中编写了Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
没有什么可以令人惊讶的,只是基本的错误检查。
转到ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
基本上,它获取MoveNext
并手动遍历每个项目。它会检查当前是否有任何项目被枚举。在枚举每个块之后,如果没有剩余任何项目,它就会爆发。
一旦检测到序列中有项目,它就会将内部IEnumerable<T>
实施的责任委托给ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
由于传递给IEnumerator<T>
的{{1}}已调用Current
,因此会生成as per the documentation返回的项目,然后递增计数,确保永远不会返回更多比ChunkSequence
项更多,并在每次迭代后移动到序列中的下一项(但如果产生的项数超过块大小,则会短路)。
如果没有剩下的项目,那么chunkSize
方法将在外部循环中进行另一次传递,但是当第二次调用InternalChunk
时,它仍然会返回false,{{3} (强调我的):
如果MoveNext传递集合的末尾,则枚举器为 位于集合中的最后一个元素和MoveNext之后 返回false。 当调查员处于此位置时,后续 调用MoveNext也会返回false,直到调用Reset。
此时,循环将中断,序列序列将终止。
这是一个简单的测试:
MoveNext
输出:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
一个重要的注意事项,如果你不排空整个子序列或在父序列中的任何一点断开,这将不。这是一个重要的警告,但如果你的用例是你将使用序列序列的每个元素,那么这将适合你。
此外,如果您使用订单,它会做一些奇怪的事情,就像Sam's did at one point一样。
答案 4 :(得分:45)
好的,这是我的看法:
public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
int chunkSize)
{
if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");
using (var e = enumerable.GetEnumerator())
while (e.MoveNext())
{
var remaining = chunkSize; // elements remaining in the current chunk
var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());
yield return e.GetChunk(innerMoveNext);
while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
}
}
private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
Func<bool> innerMoveNext)
{
do yield return e.Current;
while (innerMoveNext());
}
使用示例
var src = new [] {1, 2, 3, 4, 5, 6};
var c3 = src.Chunks(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.Chunks(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Take(2)); // {{1, 2}, {4, 5}}
说明
代码通过嵌套两个基于yield
的迭代器来工作。
外部迭代器必须跟踪内部(块)迭代器有效消耗的元素数量。这是通过使用remaining
结束innerMoveNext()
来完成的。在外迭代器产生下一个块之前,丢弃未使用的块元素。
这是必要的,因为否则当内部枚举没有(完全)消耗时(例如c3.Count()
会返回6),你会得到不一致的结果。
注意: 答案已更新,以解决@aolszowka指出的缺点。
答案 5 :(得分:14)
完全懒惰,不计算或复制:
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
{
if (len == 0)
throw new ArgumentNullException();
var enumer = source.GetEnumerator();
while (enumer.MoveNext())
{
yield return Take(enumer.Current, enumer, len);
}
}
private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
{
while (true)
{
yield return head;
if (--len == 0)
break;
if (tail.MoveNext())
head = tail.Current;
else
break;
}
}
}
答案 6 :(得分:12)
我认为以下建议是最快的。我牺牲了源Enumerable的懒惰,因为它能够使用Array.Copy,并且能够提前知道每个子列表的长度。
public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
T[] array = items as T[] ?? items.ToArray();
for (int i = 0; i < array.Length; i+=size)
{
T[] chunk = new T[Math.Min(size, array.Length - i)];
Array.Copy(array, i, chunk, 0, chunk.Length);
yield return chunk;
}
}
答案 7 :(得分:9)
我们可以改进@ JaredPar的解决方案来进行真正的懒惰评估。我们使用GroupAdjacentBy
方法生成具有相同键的连续元素组:
sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
由于这些组是逐个产生的,因此该解决方案可以有效地使用长序列或无限序列。
答案 8 :(得分:8)
System.Interactive为此目的提供了Buffer()
。一些快速测试显示性能类似于Sam的解决方案。
答案 9 :(得分:8)
几年前我写了一个Clump扩展方法。效果很好,是这里最快的实现。 :P
/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
if (source == null)
throw new ArgumentNullException("source");
if (size < 1)
throw new ArgumentOutOfRangeException("size", "size must be greater than 0");
return ClumpIterator<T>(source, size);
}
private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
Debug.Assert(source != null, "source is null.");
T[] items = new T[size];
int count = 0;
foreach (var item in source)
{
items[count] = item;
count++;
if (count == size)
{
yield return items;
items = new T[size];
count = 0;
}
}
if (count > 0)
{
if (count == size)
yield return items;
else
{
T[] tempItems = new T[count];
Array.Copy(items, tempItems, count);
yield return tempItems;
}
}
}
答案 10 :(得分:6)
这是我几个月前写的一个列表拆分程序:
public static List<List<T>> Chunk<T>(
List<T> theList,
int chunkSize
)
{
List<List<T>> result = theList
.Select((x, i) => new {
data = x,
indexgroup = i / chunkSize
})
.GroupBy(x => x.indexgroup, x => x.data)
.Select(g => new List<T>(g))
.ToList();
return result;
}
答案 11 :(得分:5)
这是一个老问题,但这是我最终的结果;它只列举一次枚举,但会为每个分区创建列表。当某些实现调用ToArray()
时,它不会受到意外行为的影响:
public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (chunkSize < 1)
{
throw new ArgumentException("Invalid chunkSize: " + chunkSize);
}
using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
{
IList<T> currentChunk = new List<T>();
while (sourceEnumerator.MoveNext())
{
currentChunk.Add(sourceEnumerator.Current);
if (currentChunk.Count == chunkSize)
{
yield return currentChunk;
currentChunk = new List<T>();
}
}
if (currentChunk.Any())
{
yield return currentChunk;
}
}
}
答案 12 :(得分:5)
我发现这个小片段能很好地完成这项工作。
public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
var offset = 0;
while (offset < source.Count)
{
yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
offset += chunkSize;
}
}
答案 13 :(得分:4)
这个怎么样?
var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3
var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
.Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
.ToList();
据我所知,GetRange()在所拍摄的项目数量方面是线性的。所以这应该表现良好。
答案 14 :(得分:4)
以下解决方案是我能想到的最紧凑的O(n)。
public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
var list = source as IList<T> ?? source.ToList();
for (int start = 0; start < list.Count; start += chunksize)
{
T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
for (int i = 0; i < chunk.Length; i++)
chunk[i] = list[start + i];
yield return chunk;
}
}
答案 15 :(得分:4)
旧代码,但这是我一直在使用的:
public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
{
var toReturn = new List<T>(max);
foreach (var item in source)
{
toReturn.Add(item);
if (toReturn.Count == max)
{
yield return toReturn;
toReturn = new List<T>(max);
}
}
if (toReturn.Any())
{
yield return toReturn;
}
}
答案 16 :(得分:4)
我们发现David B的解决方案效果最佳。但我们将其改编为更通用的解决方案:
list.GroupBy(item => item.SomeProperty)
.Select(group => new List<T>(group))
.ToArray();
答案 17 :(得分:3)
如果列表的类型为system.collections.generic,则可以使用“CopyTo”方法将数组元素复制到其他子数组。您可以指定要复制的起始元素和元素数。
您还可以制作原始列表中的3个克隆,并使用每个列表中的“RemoveRange”将列表缩小到您想要的大小。
或者只是创建一个辅助方法来为您完成。
答案 18 :(得分:2)
这是一个旧的解决方案,但我采用了不同的方法。我使用Skip
移动到所需的偏移量Take
以提取所需数量的元素:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
if (chunkSize <= 0)
throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");
var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);
return Enumerable.Range(0, nbChunks)
.Select(chunkNb => source.Skip(chunkNb*chunkSize)
.Take(chunkSize));
}
答案 19 :(得分:1)
使用模块化分区:
public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
答案 20 :(得分:1)
只需加入我的两分钱。如果你想&#34;桶&#34;列表(从左到右可视化),您可以执行以下操作:
public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
{
List<List<T>> result = new List<List<T>>();
for (int i = 0; i < numberOfBuckets; i++)
{
result.Add(new List<T>());
}
int count = 0;
while (count < source.Count())
{
var mod = count % numberOfBuckets;
result[mod].Add(source[count]);
count++;
}
return result;
}
答案 21 :(得分:1)
public static List<List<T>> GetSplitItemsList<T>(List<T> originalItemsList, short number)
{
var listGroup = new List<List<T>>();
int j = number;
for (int i = 0; i < originalItemsList.Count; i += number)
{
var cList = originalItemsList.Take(j).Skip(i).ToList();
j += number;
listGroup.Add(cList);
}
return listGroup;
}
答案 22 :(得分:1)
对于任何对打包/维护的解决方案感兴趣的人,MoreLINQ库提供了Batch
扩展方法,该方法与您请求的行为相匹配:
IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);
The Batch
implementation与Cameron MacFarland's answer相似,只是增加了一个重载,用于在返回之前对块/批处理进行转换,并且表现良好。
答案 23 :(得分:1)
另一种方法是使用Rx Buffer operator
//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;
var observableBatches = anAnumerable.ToObservable().Buffer(size);
var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
答案 24 :(得分:0)
问题是如何“使用 LINQ 将列表拆分为子列表”,但有时您可能希望这些子列表是对原始列表的引用,而不是副本。这允许您修改子列表中的原始列表。在这种情况下,这可能对您有用。
public static IEnumerable<Memory<T>> RefChunkBy<T>(this T[] array, int size)
{
if (size < 1 || array is null)
{
throw new ArgumentException("chunkSize must be positive");
}
var index = 0;
var counter = 0;
for (int i = 0; i < array.Length; i++)
{
if (counter == size)
{
yield return new Memory<T>(array, index, size);
index = i;
counter = 0;
}
counter++;
if (i + 1 == array.Length)
{
yield return new Memory<T>(array, index, array.Length - index);
}
}
}
用法:
var src = new[] { 1, 2, 3, 4, 5, 6 };
var c3 = RefChunkBy(src, 3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = RefChunkBy(src, 4); // {{1, 2, 3, 4}, {5, 6}};
// as extension method
var c3 = src.RefChunkBy(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.RefChunkBy(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Span.ToArray().Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Span.ToArray().Take(2)); // {{1, 2}, {4, 5}}
随意改进这段代码。
答案 25 :(得分:0)
无法在一个解决方案中结合所有期望的功能,例如完全懒惰,没有复制,完全通用性和安全性。最根本的原因是不能保证在访问块之前输入不会发生突变。 假设我们具有以下签名的功能:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize)
{
// Some implementation
}
那么以下使用它的方法是有问题的:
var myList = new List<int>()
{
1,2,3,4
};
var myChunks = myList.Chunk(2);
myList.RemoveAt(0);
var firstChunk = myChunks.First();
Console.WriteLine("First chunk:" + String.Join(',', firstChunk));
myList.RemoveAt(0);
var secondChunk = myChunks.Skip(1).First();
Console.WriteLine("Second chunk:" + String.Join(',', secondChunk));
// What outputs do we see for first and second chunk? Probably not what you would expect...
根据特定的实现,代码将因运行时错误而失败或产生不直观的结果。
因此,至少一个特性需要被削弱。如果您想要防弹的惰性解决方案,则需要将输入类型限制为不可变类型,即使如此,要掩盖所有用例也不是一件容易的事。但是,如果您可以控制使用方式,则仍然可以选择最通用的解决方案,只要确保以可行的方式使用它即可。否则,您可能会放弃懒惰并接受一定数量的复制。
最后,这完全取决于您的用例和要求,哪种解决方案才是您的最佳选择。
答案 26 :(得分:0)
如果源集合实现IList
public static IEnumerable<IEnumerable<T>> Chunkify<T>(this IList<T> src, int chunkSize)
{
if (src == null) throw new ArgumentNullException(nameof(src));
if (chunkSize < 1) throw new ArgumentOutOfRangeException(nameof(chunkSize), $"must be > 0, got {chunkSize}");
for(var ci = 0; ci <= src.Count/chunkSize; ci++){
yield return Window(src, ci*chunkSize, Math.Min((ci+1)*chunkSize, src.Count)-1);
}
}
private static IEnumerable<T> Window<T>(IList<T> src, int startIdx, int endIdx)
{
Console.WriteLine($"window {startIdx} - {endIdx}");
while(startIdx <= endIdx){
yield return src[startIdx++];
}
}
答案 27 :(得分:0)
检查一下!我有一个带有序列计数器和日期的元素列表。对于每次重新启动序列,我都想创建一个新列表。
例如邮件列表。
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
我想在计数器重新启动时将列表拆分为单独的列表。这是代码:
var arraylist = new List<List<dynamic>>();
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
//group by FcntUp and CommTimestamp
var query = messages.GroupBy(x => new { x.FcntUp, x.CommTimestamp });
//declare the current item
dynamic currentItem = null;
//declare the list of ranges
List<dynamic> range = null;
//loop through the sorted list
foreach (var item in query)
{
//check if start of new range
if (currentItem == null || item.Key.FcntUp < currentItem.Key.FcntUp)
{
//create a new list if the FcntUp starts on a new range
range = new List<dynamic>();
//add the list to the parent list
arraylist.Add(range);
}
//add the item to the sublist
range.Add(item);
//set the current item
currentItem = item;
}
答案 28 :(得分:0)
可以使用无限生成器:
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private static void DoIt(IEnumerable<int> a)
{
Console.WriteLine(String.Join(" ", a));
foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
Console.WriteLine(String.Join(" ", x));
Console.WriteLine();
}
public static void Main()
{
DoIt(new int[] {1});
DoIt(new int[] {1, 2});
DoIt(new int[] {1, 2, 3});
DoIt(new int[] {1, 2, 3, 4});
DoIt(new int[] {1, 2, 3, 4, 5});
DoIt(new int[] {1, 2, 3, 4, 5, 6});
}
}
演示代码:https://ideone.com/GKmL7M
1
1 2
1 2 3
1 2 3
1 2 3 4
1 2 3
1 2 3 4 5
1 2 3
1 2 3 4 5 6
1 2 3
4 5 6
/*css/*
.time-label{
position: relative;
display: inline-block;
}
.popup-info{
width: 100%;
height: 100%;
border: none;
background-color: #2e304f;
color: #f4f3ef;
cursor: pointer;
}
.popup-info2{
min-height: 100%;
visibility: hidden;
background-color: #555;
color: #fff;
border-radius: 6px;
padding: 20px;
position: absolute;
z-index: 1;
bottom: 0;
}
.time-label .show {
visibility: visible;
-webkit-animation: fadeIn 0,5s;
animation: fadeIn 0.5s;
}
/* JS Add animation (fade in the popup) */
@-webkit-keyframes fadeIn {
from {opacity: 0;}
to {opacity: 1;}
}
function programpopups(a) {
a.parentNode.getElementsByClassName("popup-info2")
[0].classList.toggle("show");
}
/* HtML/*
<div class="event-and-date">
<div class="time-frame">
<p>15:15<br>17:15</div>
<div class="time-label">
<button onclick="programpopups(this)" class="popup-info">
FORM DIN EGEN JYDEPOTTE I LER
<br><br>STED: Varde Torv <br>
ARRANGØR: Varde Kommune</button>
<span class="popup-info2">
Der blev lavet og solgt
mange jydepotter i middelalderens Varde.
Form din egen jydepotte i ler og bliv
inspireret af middelalderens traditioner
v. kunstner Per Ottesen.</span>
</div>
</div>
但实际上我更愿意在没有linq的情况下编写相应的方法。
答案 29 :(得分:0)
如同Sam Saffron的方法那样具有表现性。
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");
return BatchImpl(source, size).TakeWhile(x => x.Any());
}
static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
var values = new List<T>();
var group = 1;
var disposed = false;
var e = source.GetEnumerator();
try
{
while (!disposed)
{
yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
group++;
}
}
finally
{
if (!disposed)
e.Dispose();
}
}
static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
var min = (group - 1) * size + 1;
var max = group * size;
var hasValue = false;
while (values.Count < min && e.MoveNext())
{
values.Add(e.Current);
}
for (var i = min; i <= max; i++)
{
if (i <= values.Count)
{
hasValue = true;
}
else if (hasValue = e.MoveNext())
{
values.Add(e.Current);
}
else
{
dispose();
}
if (hasValue)
yield return values[i - 1];
else
yield break;
}
}
}
答案 30 :(得分:0)
我接受了主要答案,并将其作为IOC容器来确定拆分的位置。 (对于谁真的只想分开3个项目,在搜索答案时阅读这篇文章?)
此方法允许根据需要拆分任何类型的项目。
public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
int groupIndex = 0;
return main.Select( item => new
{
Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex),
Value = item
})
.GroupBy( it2 => it2.Group)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
因此对于OP,代码将是
var it = new List<string>()
{ "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
int index = 0;
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );
答案 31 :(得分:-1)
插入我的两分钱......
通过使用源的列表类型进行分块,我发现了另一个非常紧凑的解决方案:
public static IEnumerable<IEnumerable<TSource>> Chunk<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
// copy the source into a list
var chunkList = source.ToList();
// return chunks of 'chunkSize' items
while (chunkList.Count > chunkSize)
{
yield return chunkList.GetRange(0, chunkSize);
chunkList.RemoveRange(0, chunkSize);
}
// return the rest
yield return chunkList;
}