n
LINQ를 사용 하여 컬렉션을 여러 부분으로 분할하는 좋은 방법이 있습니까? 물론 균등할 필요는 없습니다.
즉, 컬렉션을 하위 컬렉션으로 나누고 싶습니다. 각 하위 컬렉션에는 요소의 하위 집합이 포함되어 있으며 마지막 컬렉션은 비정형 일 수 있습니다.
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;
}
}
.AsEnumerable()
필요하지 않습니다. IGrouping <T>는 이미 IEnumerable <T>입니다.
편집 : 좋아, 내가 질문을 잘못 읽은 것 같습니다. 나는 그것을 "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);
}
}
yield return
. 한 번에 하나의 배치 가 메모리에 있어야 하지만 그게 전부입니다.
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));
}
}
var dept = {1,2,3,4,5}
있습니다. 분할 후 결과는 비슷 dept1 = {1,3,5}
하고 dept2 = { 2,4 }
곳 parts = 2
. 그러나 필요 난 결과는 dept1 = {1,2,3}
과dept2 = {4,5}
int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);
한 다음 .GroupBy(x => x.index / columnLength)
. 한 가지 단점은 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);
}
}
최종 결과는 매우 간단한 코드에 의존하는 매우 효율적이고 스트리밍 및 지연 구현입니다.
즐겨!
나는 이것을 사용한다 :
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
return instance
.Select((value, index) => new { Index = index, Value = value })
.GroupBy(i => i.Index / partitionSize)
.Select(i => i.Select(i2 => i2.Value));
}
이것은 메모리 효율적이며 가능한 한 (배치 당) 실행을 연기하고 선형 시간 O (n)에서 작동합니다.
public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
{
List<T> batch = new List<T>(batchSize);
foreach (var item in items)
{
batch.Add(item);
if (batch.Count >= batchSize)
{
yield return batch;
batch = new List<T>();
}
}
if (batch.Count != 0)
{
//can't be batch size or would've yielded above
batch.TrimExcess();
yield return batch;
}
}
이 질문 (및 그 사촌들)에 대한 훌륭한 답변이 많이 있습니다. 필자는 이것을 직접 필요로했고 소스 컬렉션을 목록으로 처리 할 수있는 시나리오에서 효율적이고 오류를 허용하도록 설계된 솔루션을 만들었습니다. 지연 반복을 사용하지 않으므로 메모리 압력을 가할 수있는 알 수없는 크기의 컬렉션에는 적합하지 않을 수 있습니다.
static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
IList<T[]> result = null;
if (source != null && batchsize > 0)
{
var list = source as List<T> ?? source.ToList();
if (list.Count > 0)
{
result = new List<T[]>();
for (var index = 0; index < list.Count; index += batchsize)
{
var rangesize = Math.Min(batchsize, list.Count - index);
result.Add(list.GetRange(index, rangesize).ToArray());
}
}
}
return result ?? Enumerable.Empty<T[]>().ToList();
}
static public void TestGetChunks()
{
var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
foreach (var chunk in ids.GetChunks(20))
{
Console.WriteLine("[{0}]", String.Join(",", chunk));
}
}
GetRange 및 Math.Min을 사용하는이 질문 군에서 몇 가지 답변을 보았습니다. 그러나 전반적으로 이것이 오류 검사 및 효율성 측면에서 더 완벽한 솔루션이라고 생각합니다.
protected List<List<int>> MySplit(int MaxNumber, int Divider)
{
List<List<int>> lst = new List<List<int>>();
int ListCount = 0;
int d = MaxNumber / Divider;
lst.Add(new List<int>());
for (int i = 1; i <= MaxNumber; i++)
{
lst[ListCount].Add(i);
if (i != 0 && i % d == 0)
{
ListCount++;
d += MaxNumber / Divider;
lst.Add(new List<int>());
}
}
return lst;
}
Great Answers, 내 시나리오에 대해 수락 된 답변을 테스트했는데 순서가 유지되지 않는 것 같습니다. 질서를 유지하는 Nawfal의 훌륭한 답변도 있습니다. 그러나 내 시나리오에서는 나머지를 정규화 된 방식으로 나누고 싶었습니다. 내가 본 모든 답변은 나머지 또는 시작 또는 끝에 퍼졌습니다.
내 대답은 또한 나머지가 더 정규화 된 방식으로 확산됩니다.
static class Program
{
static void Main(string[] args)
{
var input = new List<String>();
for (int k = 0; k < 18; ++k)
{
input.Add(k.ToString());
}
var result = splitListIntoSmallerLists(input, 15);
int i = 0;
foreach(var resul in result){
Console.WriteLine("------Segment:" + i.ToString() + "--------");
foreach(var res in resul){
Console.WriteLine(res);
}
i++;
}
Console.ReadLine();
}
private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
{
if (i_numberOfSmallerLists <= 0)
throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");
int normalizedSpreadRemainderCounter = 0;
int normalizedSpreadNumber = 0;
//e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2
int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;
int remainder = i_bigList.Count % i_numberOfSmallerLists;
int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
//In case remainder > 0 we want to spread the remainder equally between the others
if (remainder > 0)
{
if (minimumNumberOfPartsInEachSmallerList > 0)
{
normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);
}
else
{
normalizedSpreadNumber = 1;
}
}
List<List<T>> retVal = new List<List<T>>(outputSize);
int inputIndex = 0;
for (int i = 0; i < outputSize; ++i)
{
retVal.Add(new List<T>());
if (minimumNumberOfPartsInEachSmallerList > 0)
{
retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
inputIndex += minimumNumberOfPartsInEachSmallerList;
}
//If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
if (remainder > 0)
{
if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
{
retVal[i].Add(i_bigList[inputIndex]);
remainder--;
inputIndex++;
normalizedSpreadRemainderCounter=0;
}
else
{
normalizedSpreadRemainderCounter++;
}
}
}
return retVal;
}
}
이 부분의 순서가 그다지 중요하지 않은 경우 다음을 시도해 볼 수 있습니다.
int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;
var result =
array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);
// or
var result2 =
from i in array.Select((value, index) => new { Value = value, Index = index })
group i.Value by i.Index % n into g
select g;
그러나 이들은 어떤 이유로 IEnumerable <IEnumerable <int >>로 캐스팅 될 수 없습니다.
이것은 멋지고 짧은 내 코드입니다.
<Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
Dim result As New List(Of List(Of T))
For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
Next
Return result
End Function
나는 문자열이있는 것과 같은 분할을 찾고 있었으므로 전체 목록이 첫 번째 부분뿐만 아니라 일부 규칙에 따라 분할됩니다. 이것이 내 솔루션입니다.
List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
splitted.Add(sequence.Take(splitIndex).ToList() );
sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };
int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;
var seqItems = from aItem in items
group aItem by
(itemIndex++ < nextGroup)
?
nextGroup / groupSize
:
(nextGroup += groupSize) / groupSize
into itemGroup
select itemGroup.AsEnumerable();
방금이 스레드를 발견했으며 여기에있는 대부분의 솔루션에는 컬렉션에 항목을 추가하고 각 페이지를 반환하기 전에 효과적으로 구체화하는 것이 포함됩니다. 이것은 두 가지 이유로 좋지 않습니다. 첫째로 페이지가 크면 페이지를 채우는 데 메모리 오버 헤드가 있고, 둘째로 다음 레코드로 진행할 때 이전 레코드를 무효화하는 반복기가 있습니다 (예 : 열거 자 메서드 내에서 DataReader를 래핑하는 경우). .
이 솔루션은 항목을 임시 컬렉션에 캐시 할 필요가 없도록 두 개의 중첩 된 열거 자 메서드를 사용합니다. 외부 및 내부 반복자는 동일한 열거 형을 순회하므로 반드시 동일한 열거자를 공유하므로 현재 페이지 처리를 완료 할 때까지 외부 반복기를 진행하지 않는 것이 중요합니다. 즉, 현재 페이지 전체를 반복하지 않기로 결정한 경우 다음 페이지로 이동하면이 솔루션이 자동으로 페이지 경계까지 반복됩니다.
using System.Collections.Generic;
public static class EnumerableExtensions
{
/// <summary>
/// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="enumerable">The source enumerable</param>
/// <param name="pageSize">The number of elements to return in each page</param>
/// <returns></returns>
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
{
var enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
var indexWithinPage = new IntByRef { Value = 0 };
yield return SubPartition(enumerator, pageSize, indexWithinPage);
// Continue iterating through any remaining items in the page, to align with the start of the next page
for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
{
if (!enumerator.MoveNext())
{
yield break;
}
}
}
}
private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
{
for (; index.Value < pageSize; index.Value++)
{
yield return enumerator.Current;
if (!enumerator.MoveNext())
{
yield break;
}
}
}
private class IntByRef
{
public int Value { get; set; }
}
}