n
LINQ를 사용 하여 컬렉션을 여러 부분으로 분할하는 좋은 방법이 있습니까? 물론 균등할 필요는 없습니다.
즉, 컬렉션을 하위 컬렉션으로 나누고 싶습니다. 각 하위 컬렉션에는 요소의 하위 집합이 포함되어 있으며 마지막 컬렉션은 비정형 일 수 있습니다.
답변
순수한 linq와 가장 간단한 해결책은 아래와 같습니다.
static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int i = 0;
var splits = from item in list
group item by i++ % parts into part
select part.AsEnumerable();
return splits;
}
}
답변
편집 : 좋아, 내가 질문을 잘못 읽은 것 같습니다. 나는 그것을 “n 개”라기보다는 “길이 n 개”로 읽었다. 도! 답변 삭제 고려 중 …
(원래 답변)
LINQ to Objects에 대한 추가 항목 집합에 하나를 작성하려고하지만 기본 제공 분할 방법이 있다고 생각하지 않습니다. Marc Gravell은 여기 에 구현이 있지만 읽기 전용 뷰를 반환하도록 수정합니다.
public static IEnumerable<IEnumerable<T>> Partition<T>
(this IEnumerable<T> source, int size)
{
T[] array = null;
int count = 0;
foreach (T item in source)
{
if (array == null)
{
array = new T[size];
}
array[count] = item;
count++;
if (count == size)
{
yield return new ReadOnlyCollection<T>(array);
array = null;
count = 0;
}
}
if (array != null)
{
Array.Resize(ref array, count);
yield return new ReadOnlyCollection<T>(array);
}
}
답변
static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
return list.Select((item, index) => new {index, item})
.GroupBy(x => x.index % parts)
.Select(x => x.Select(y => y.item));
}
}
답변
좋아, 반지에 모자를 던질 게. 내 알고리즘의 장점 :
- 값 비싼 곱셈, 나눗셈 또는 모듈러스 연산자 없음
- 모든 작업은 O (1)입니다 (아래 참고 참조).
- IEnumerable <> 소스에서 작동합니다 (Count 속성이 필요하지 않음).
- 단순한
코드:
public static IEnumerable<IEnumerable<T>>
Section<T>(this IEnumerable<T> source, int length)
{
if (length <= 0)
throw new ArgumentOutOfRangeException("length");
var section = new List<T>(length);
foreach (var item in source)
{
section.Add(item);
if (section.Count == length)
{
yield return section.AsReadOnly();
section = new List<T>(length);
}
}
if (section.Count > 0)
yield return section.AsReadOnly();
}
아래 주석에서 지적했듯이이 접근 방식은 거의 동일한 길이의 고정 된 수의 섹션을 요구하는 원래 질문을 실제로 해결하지 않습니다. 즉, 내 접근 방식을 사용하여 원래 질문을 다음과 같이 호출하여 해결할 수 있습니다.
myEnum.Section(myEnum.Count() / number_of_sections + 1)
이 방식으로 사용하면 Count () 연산이 O (N)이므로 접근 방식은 더 이상 O (1)이 아닙니다.
답변
이것은 받아 들여지는 대답과 동일하지만 훨씬 더 간단한 표현입니다.
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
int numOfParts)
{
int i = 0;
return items.GroupBy(x => i++ % numOfParts);
}
위의 방법은을 IEnumerable<T>
크기가 같거나 크기가 비슷한 N 개의 청크로 분할 합니다.
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
위의 방법은를 IEnumerable<T>
원하는 고정 크기의 청크로 분할하고 총 청크 수는 중요하지 않습니다. 이것은 질문의 내용이 아닙니다.
이 Split
방법 의 문제점 은 속도가 느리다는 것 외에도 그룹화가 각 위치에 대해 N의 i 배수를 기반으로 수행된다는 의미에서 출력을 스크램블하거나 즉, 청크를 얻지 못한다는 것입니다. 원래 순서대로.
여기에있는 거의 모든 대답은 순서를 유지하지 않거나 분할하지 않고 분할하는 것에 관한 것이거나 명백히 잘못된 것입니다. 더 빠르고, 순서는 유지하지만 좀 더 장황한 방법을 시도해보십시오.
public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items,
int numberOfChunks)
{
if (numberOfChunks <= 0 || numberOfChunks > items.Count)
throw new ArgumentOutOfRangeException("numberOfChunks");
int sizePerPacket = items.Count / numberOfChunks;
int extra = items.Count % numberOfChunks;
for (int i = 0; i < numberOfChunks - extra; i++)
yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);
int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
for (int i = 0; i < extra; i++)
yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}
여기 에서 Partition
작업에 해당하는 방법
답변
이전에 게시 한 파티션 기능을 자주 사용하고 있습니다. 그것의 유일한 나쁜 점은 완전히 스트리밍되지 않는다는 것입니다. 시퀀스에서 몇 가지 요소로 작업하는 경우 문제가되지 않습니다. 시퀀스에서 10,000 개 이상의 요소로 작업을 시작했을 때 새로운 솔루션이 필요했습니다.
다음 솔루션은 훨씬 더 복잡하지만 (더 많은 코드!) 매우 효율적입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace LuvDaSun.Linq
{
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
{
/*
return enumerable
.Select((item, index) => new { Item = item, Index = index, })
.GroupBy(item => item.Index / partitionSize)
.Select(group => group.Select(item => item.Item) )
;
*/
return new PartitioningEnumerable<T>(enumerable, partitionSize);
}
}
class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
{
IEnumerable<T> _enumerable;
int _partitionSize;
public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
{
_enumerable = enumerable;
_partitionSize = partitionSize;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
{
IEnumerator<T> _enumerator;
int _partitionSize;
public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
{
_enumerator = enumerator;
_partitionSize = partitionSize;
}
public void Dispose()
{
_enumerator.Dispose();
}
IEnumerable<T> _current;
public IEnumerable<T> Current
{
get { return _current; }
}
object IEnumerator.Current
{
get { return _current; }
}
public void Reset()
{
_current = null;
_enumerator.Reset();
}
public bool MoveNext()
{
bool result;
if (_enumerator.MoveNext())
{
_current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
result = true;
}
else
{
_current = null;
result = false;
}
return result;
}
}
class PartitionEnumerable<T> : IEnumerable<T>
{
IEnumerator<T> _enumerator;
int _partitionSize;
public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
{
_enumerator = enumerator;
_partitionSize = partitionSize;
}
public IEnumerator<T> GetEnumerator()
{
return new PartitionEnumerator<T>(_enumerator, _partitionSize);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class PartitionEnumerator<T> : IEnumerator<T>
{
IEnumerator<T> _enumerator;
int _partitionSize;
int _count;
public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
{
_enumerator = enumerator;
_partitionSize = partitionSize;
}
public void Dispose()
{
}
public T Current
{
get { return _enumerator.Current; }
}
object IEnumerator.Current
{
get { return _enumerator.Current; }
}
public void Reset()
{
if (_count > 0) throw new InvalidOperationException();
}
public bool MoveNext()
{
bool result;
if (_count < _partitionSize)
{
if (_count > 0)
{
result = _enumerator.MoveNext();
}
else
{
result = true;
}
_count++;
}
else
{
result = false;
}
return result;
}
}
}
즐겨!
답변
흥미로운 스레드. Split / Partition의 스트리밍 버전을 얻으려면 열거자를 사용하고 확장 메서드를 사용하여 열거 자에서 시퀀스를 생성 할 수 있습니다. yield를 사용하여 명령형 코드를 기능 코드로 변환하는 것은 실제로 매우 강력한 기술입니다.
먼저 요소 수를 지연 시퀀스로 바꾸는 열거 자 확장 :
public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
while (count > 0)
{
yield return enumerator.Current;
if (--count > 0 && !enumerator.MoveNext()) yield break;
}
}
그리고 시퀀스를 분할하는 열거 가능한 확장 :
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
var enumerator = seq.GetEnumerator();
while (enumerator.MoveNext())
{
yield return enumerator.TakeFromCurrent(partitionSize);
}
}
최종 결과는 매우 간단한 코드에 의존하는 매우 효율적이고 스트리밍 및 지연 구현입니다.
즐겨!