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]
또한 결과 목록 크기 가이 함수의 매개 변수가되어야합니다.
답변
다음 코드를 시도하십시오.
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의 그룹으로 그룹화의 효과와가 IEnumerable
의 List
A와 List
의 List
들
답변
이 질문은 조금 오래되었지만 방금 작성했으며 다른 제안 된 솔루션보다 조금 더 우아하다고 생각합니다.
/// <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);
}
}
답변
일반적으로 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의 접근 방식을 접근 방식은 열거 형을 한 번만 걷기 때문에 위의 테스트를 비행 색상으로 통과시킵니다.
문제는 다른 결함이 있고 각 청크의 모든 항목을 구체화한다는 것입니다.이 접근법의 문제점은 메모리가 부족하다는 것입니다.
실행을 설명하기 위해 :
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]
이 답변의 첫 번째 개정판 과 같은 많은 최적의 솔루션 이 거기에서 실패했습니다. 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();
}
}
}
또한 범위를 벗어난 청크의 비 순차적 반복에 대해 소개 할 수있는 최적화 라운드도 있습니다.
어떤 방법을 선택해야합니까? 그것은 당신이 해결하려는 문제에 전적으로 달려 있습니다. 첫 번째 결함에 관심이 없다면 간단한 답변이 매우 매력적입니다.
참고 대부분의 방법과 마찬가지로, 이것은 당신이 당신이 개정 할 필요가 스레드 안전 확인하고자하는 경우 멀티 스레딩을 위해, 물건 이상한 얻을 수있는 안전하지 않습니다 EnumeratorWrapper
.
답변
당신은 할 수 사용하는 쿼리의 번호를 사용 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의 답변에 비추어 볼 때 , 나는 이것을하지 않고 이것을 수행하는 더 쉬운 방법이 있다고 느꼈습니다.
- 목록을 다시 반복합니다 (원래는하지 않았습니다)
- 청크를 해제하기 전에 그룹으로 항목을 구체화 (큰 청크의 경우 메모리 문제가 있음)
- 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);
}
기본적인 오류 점검만으로 놀라운 것은 없습니다.
다음으로 넘어갑니다 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);
}
기본적으로 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());
}
이후 MoveNext
이미 호출 된 IEnumerator<T>
전달 ChunkSequence
, 그것은에 의해 반환 된 항목을 얻을 수 Current
있는지 결코 이상 반환 할 수있게되지 다음 수를 증가chunkSize
항목마다 반복 한 후 순서의 다음 항목으로 이동 (그러나 수의 경우 단락 생산량은 청크 크기를 초과합니다).
항목이 남아 있지 않으면 InternalChunk
메소드는 외부 루프에 다른 패스를 전달하지만 MoveNext
두 번째로 호출 되면 문서 (강조 광산)에 따라 여전히 false를 반환합니다 .
MoveNext가 컬렉션의 끝을 통과하면 열거자는 컬렉션의 마지막 요소 뒤에 위치하고 MoveNext는 false를 반환합니다. 열거자가이 위치에있을 때 ResetNext가 호출 될 때까지 MoveNext에 대한 후속 호출도 false를 반환합니다.
이 시점에서 루프가 중단되고 시퀀스 시퀀스가 종료됩니다.
이것은 간단한 테스트입니다.
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();
}
}
산출:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
중요한 점 은 전체 하위 시퀀스를 배수하거나 상위 시퀀스의 어느 시점에서든 중단하지 않으면 작동하지 않습니다. 사용 사례 당신이 소비하는 것입니다 경우에 중요한주의이지만, 모든를 시퀀스 시퀀스의 요소를 이것이 효과가 있습니다.
또한 Sam이 한 시점에서했던 것처럼 주문을 가지고 놀면 이상한 일을 할 것 입니다.
답변
좋아, 여기에 내가 가지고있다 :
- 완전히 게으른 : 무한 열거 가능
- 중간 복사 / 버퍼 없음
- O (n) 실행 시간
- 내부 시퀀스가 부분적으로 만 소비되는 경우에도 작동
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가 지적한 단점을 해결하기 위해 답변이 업데이트되었습니다.
답변
완전히 게 으르거나 계산 또는 복사하지 않음 :
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;
}
}
}
답변
다음 제안이 가장 빠를 것이라고 생각합니다. Array.Copy를 사용할 수있는 기능으로 소스 Enumerable의 게으름을 희생하고 각 하위 목록의 길이를 미리 알고 있습니다.
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;
}
}