Fel*_*ima 361 c# linq data-structures
有没有什么方法可以将项目索引作为每个拆分的分隔符List<SomeObject>
分成几个单独的列表SomeObject
?
让我举例说明:
我有一个List<SomeObject>
,我需要一个List<List<SomeObject>>
或List<SomeObject>[]
,所以这些结果列表中的每一个将包含一组3个原始列表项(顺序).
例如.:
原始清单: [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]
我还需要将结果列表大小作为此函数的参数.
Jar*_*Par 357
请尝试以下代码.
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();
}
Run Code Online (Sandbox Code Playgroud)
我们的想法是首先按索引对元素进行分组.除以三有其分组为3.组,那么每组转换到一个列表的效果和IEnumerable
的List
到List
的List
小号
Cas*_*eyB 310
这个问题有点陈旧,但我刚刚写了这篇文章,我认为它比其他提议的解决方案更优雅:
/// <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);
}
}
Run Code Online (Sandbox Code Playgroud)
Sam*_*ron 94
一般来说,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;
}
}
Run Code Online (Sandbox Code Playgroud)
这将避免大规模的呼叫链.尽管如此,这种方法有一个普遍的缺陷.它实现了每个块的两个枚举,以突出显示尝试运行的问题:
foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
Console.WriteLine(item);
}
// wait forever
Run Code Online (Sandbox Code Playgroud)
为了克服这个问题,我们可以尝试使用Cameron的方法,该方法通过上述测试以飞行颜色进行,因为它只进行一次枚举.
麻烦的是它有一个不同的缺陷,它实现了每个块中的每个项目,这种方法的麻烦在于你在内存上运行得很高.
为了说明尝试运行:
foreach (var item in Enumerable.Range(1, int.MaxValue)
.Select(x => x + new string('x', 100000))
.Clump(10000).Skip(100).First())
{
Console.Write('.');
}
// OutOfMemoryException
Run Code Online (Sandbox Code Playgroud)
最后,任何实现都应该能够处理块的乱序迭代,例如:
Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]
Run Code Online (Sandbox Code Playgroud)
许多高度优化的解决方案,例如我对此答案的第一次修订失败了 在casperOne的优化答案中可以看到同样的问题.
要解决所有这些问题,您可以使用以下内容:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
您还可以为块的无序迭代引入一轮优化,这超出了范围.
至于你应该选择哪种方法?这完全取决于您试图解决的问题.如果你不关心第一个缺陷,简单的答案是非常吸引人的.
请注意,与大多数方法一样,这对于多线程是不安全的,如果您希望使线程安全,您可能需要修改,这会很奇怪EnumeratorWrapper
.
Lib*_*tad 66
.NET 6.0在 System.Linq 命名空间中添加了一个新的本机Chunk方法:
\npublic static System.Collections.Generic.IEnumerable<TSource[]> Chunk<TSource> (\n this System.Collections.Generic.IEnumerable<TSource> source, int size);\n
Run Code Online (Sandbox Code Playgroud)\n使用这种新方法,除最后一个块之外的每个块的大小都将为size
。最后一个块将包含剩余的元素,并且可能具有较小的大小。
这是一个例子:
\nvar list = Enumerable.Range(1, 100);\nvar chunkSize = 10;\n\nforeach(var chunk in list.Chunk(chunkSize)) //Returns a chunk with the correct size. \n{\n Parallel.ForEach(chunk, (item) =>\n {\n //Do something Parallel here. \n Console.WriteLine(item);\n });\n}\n
Run Code Online (Sandbox Code Playgroud)\n您\xe2\x80\x99 可能会想,为什么不使用 Skip 和 Take 呢?确实如此,我认为这更加简洁,并且使内容更具可读性。
\ncas*_*One 65
您可以使用许多使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以调用它并启用LINQ,以便对结果序列执行其他操作.
根据Sam的回答,我觉得有一种更简单的方法可以做到这一点:
也就是说,这是另一个传递,我已经编写了一个扩展方法来IEnumerable<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);
}
Run Code Online (Sandbox Code Playgroud)
没有什么令人惊讶的,只是基本的错误检查.
继续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);
}
Run Code Online (Sandbox Code Playgroud)
基本上,它获取IEnumerator<T>
并手动遍历每个项目.它会检查当前是否有任何项目被枚举.在枚举每个块之后,如果没有剩余任何项目,它就会爆发.
一旦检测到序列中有项目,它就会将内部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());
}
Run Code Online (Sandbox Code Playgroud)
由于MoveNext
已经调用了IEnumerator<T>
传递给ChunkSequence
它,它产生返回的项目Current
然后递增计数,确保永远不会返回多于chunkSize
项目并在每次迭代后移动到序列中的下一个项目(但如果数量为产生的物品超过了块大小).
如果没有剩下的项目,那么该InternalChunk
方法将在外部循环中进行另一次传递,但是当MoveNext
第二次调用时,它仍将返回false,如文档所示(强调我的):
如果MoveNext传递集合的末尾,则枚举数位于集合中的最后一个元素之后,MoveNext返回false.当枚举器处于此位置时,后续对MoveNext的调用也会返回false,直到调用Reset.
此时,循环将中断,序列序列将终止.
这是一个简单的测试:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Run Code Online (Sandbox Code Playgroud)
一个重要的注意,这会不会,如果你不漏整个子序列或亲本序列中任何一点突破的工作.这是一个重要的警告,但如果您的用例是您将使用序列序列的每个元素,那么这将适合您.
另外,如果你玩这个命令,它会做一些奇怪的事情,就像Sam在某一点上做的那样.
3dG*_*ber 46
好的,这是我的看法:
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());
}
Run Code Online (Sandbox Code Playgroud)
示例用法
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}}
Run Code Online (Sandbox Code Playgroud)
说明
代码通过嵌套两个yield
基于迭代器来工作.
外部迭代器必须跟踪内部(块)迭代器有效消耗的元素数量.这是通过闭合在完成remaining
用innerMoveNext()
.在外迭代器产生下一个块之前,丢弃未使用的块元素.这是必要的,因为否则当内部枚举没有(完全)消耗时(例如c3.Count()
将返回6),你会得到不一致的结果.
注意: 答案已经更新,以解决@aolszowka指出的缺点.
xto*_*ofs 16
完全懒惰,不计数或复制:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*and 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;
}
}
Run Code Online (Sandbox Code Playgroud)
我们可以改进@JaredPar的解决方案来进行真正的懒惰评估.我们使用一种GroupAdjacentBy
方法,产生具有相同键的连续元素组:
sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))
Run Code Online (Sandbox Code Playgroud)
由于这些组是逐个产生的,因此该解决方案可以有效地使用长序列或无限序列.
对于对打包/维护解决方案感兴趣的任何人,MoreLINQ库提供了Batch
与您请求的行为相匹配的扩展方法:
IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);
Run Code Online (Sandbox Code Playgroud)
该Batch
实现类似于Cameron MacFarland 的 answer,添加了用于在返回之前转换块/批处理的重载,并且性能非常好。
几年前我写了一个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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是我几个月前写的一个列表拆分程序:
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;
}
Run Code Online (Sandbox Code Playgroud)
我们发现 David B 的解决方案效果最好。但我们将其调整为更通用的解决方案:
list.GroupBy(item => item.SomeProperty)
.Select(group => new List<T>(group))
.ToArray();
Run Code Online (Sandbox Code Playgroud)
这个如何?
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();
Run Code Online (Sandbox Code Playgroud)
据我所知,GetRange()在所取项目数方面是线性的。所以这应该表现良好。
这是一个老问题,但这是我最终得到的; 它只列举一次枚举,但会为每个分区创建列表.当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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
旧代码,但这是我一直在使用的:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我发现这个小片段做得很好.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
193156 次 |
最近记录: |