Bạn có thể sử dụng một số truy vấn sử dụng Take
và Skip
, nhưng điều đó sẽ thêm quá nhiều lần lặp vào danh sách ban đầu, tôi tin.
Thay vào đó, tôi nghĩ bạn nên tạo một trình vòng lặp của riêng bạn, như vậy:
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;
}
}
Sau đó, bạn có thể gọi đây và nó được bật LINQ để bạn có thể thực hiện các thao tác khác trên chuỗi kết quả.
Trước câu trả lời của Sam , tôi cảm thấy có một cách dễ dàng hơn để làm điều này mà không cần:
- Lặp lại danh sách một lần nữa (điều mà trước đây tôi không làm)
- Vật chất hóa các vật phẩm trong nhóm trước khi phát hành khối (đối với khối vật phẩm lớn, sẽ có vấn đề về bộ nhớ)
- Tất cả các mã mà Sam đã đăng
Điều đó nói rằng, đây là một đường chuyền khác, mà tôi đã mã hóa theo phương thức mở rộng để IEnumerable<T>
gọi 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);
}
Không có gì đáng ngạc nhiên trên đó, chỉ cần kiểm tra lỗi cơ bản.
Chuyển sang 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);
}
Về cơ bản, nó được IEnumerator<T>
lặp và thủ công lặp qua từng mục. Nó kiểm tra xem nếu có bất kỳ mục nào hiện đang được liệt kê. Sau khi mỗi đoạn được liệt kê thông qua, nếu không còn vật phẩm nào, nó sẽ vỡ ra.
Khi phát hiện có các mục trong chuỗi, nó ủy thác trách nhiệm IEnumerable<T>
thực hiện bên trong để 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());
}
Vì MoveNext
đã được gọi vào IEnumerator<T>
được chuyển đến ChunkSequence
, nó mang lại vật phẩm được trả về Current
và sau đó tăng số đếm, đảm bảo không bao giờ trả lại nhiều hơn chunkSize
vật phẩm và chuyển sang mục tiếp theo trong chuỗi sau mỗi lần lặp (nhưng bị ngắn mạch nếu số lượng các mặt hàng mang lại vượt quá kích thước chunk).
Nếu không còn mục nào, thì InternalChunk
phương thức sẽ thực hiện một lần chuyển tiếp khác trong vòng lặp bên ngoài, nhưng khi MoveNext
được gọi lần thứ hai, nó vẫn trả về false, theo tài liệu (nhấn mạnh của tôi):
Nếu MoveNext vượt qua phần cuối của bộ sưu tập, bộ liệt kê được định vị sau phần tử cuối cùng trong bộ sưu tập và MoveNext trả về sai. Khi điều tra viên ở vị trí này, các cuộc gọi tiếp theo tới MoveNext cũng trả về false cho đến khi Reset được gọi.
Tại thời điểm này, vòng lặp sẽ bị phá vỡ và chuỗi các chuỗi sẽ chấm dứt.
Đây là một bài kiểm tra đơn giản:
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();
}
}
Đầu ra:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Một lưu ý quan trọng, điều này sẽ không hoạt động nếu bạn không rút toàn bộ chuỗi con hoặc ngắt tại bất kỳ điểm nào trong chuỗi cha. Đây là một cảnh báo quan trọng, nhưng nếu trường hợp sử dụng của bạn là bạn sẽ tiêu thụ mọi yếu tố của chuỗi trình tự, thì điều này sẽ làm việc cho bạn.
Ngoài ra, nó sẽ làm những điều kỳ lạ nếu bạn chơi theo thứ tự, giống như Sam đã làm tại một thời điểm .