n
LINQ ile bir koleksiyonu parçalara ayırmanın güzel bir yolu var mı ? Tabii ki eşit olarak değil.
Yani, koleksiyonu alt koleksiyonlara bölmek istiyorum, her biri son koleksiyonun parçalanabileceği öğelerin bir alt kümesini içerir.
n
LINQ ile bir koleksiyonu parçalara ayırmanın güzel bir yolu var mı ? Tabii ki eşit olarak değil.
Yani, koleksiyonu alt koleksiyonlara bölmek istiyorum, her biri son koleksiyonun parçalanabileceği öğelerin bir alt kümesini içerir.
Yanıtlar:
Saf bir bağlantı ve en basit çözüm aşağıda gösterildiği gibidir.
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()
gerekli değil, IGrouping <T> zaten bir IEnumerable <T>.
DÜZENLEME: Tamam, soruyu yanlış anladım gibi görünüyor. Bunu "n adet" yerine "n adet uzunluğunda parçalar" olarak okudum. Doh! Cevabı silme düşünülüyor ...
(Orijinal cevap)
LINQ to Objects'e eklemelerimde bir tane yazmak niyetinde olsam da, yerleşik bir bölümleme yolu olduğuna inanmıyorum. Marc Gravell'in burada bir uygulaması var, ancak muhtemelen salt okunur bir görünüm döndürmek için değiştirecek olsam da:
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
. Biri gerektiren toplu bir anda bellekte olması, ama hepsi bu.
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}
. Bölündükten sonra sonuç nasıl dept1 = {1,3,5}
ve dept2 = { 2,4 }
nerede olur parts = 2
. Ama ihtiyaç i sonucudur dept1 = {1,2,3}
vedept2 = {4,5}
int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);
sonra ile bölme yaptım .GroupBy(x => x.index / columnLength)
. Bir dezavantajı Count () listeyi numaralandırmasıdır.
Tamam, ringe şapkamı atacağım. Algoritmamın avantajları:
Kod:
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();
}
Aşağıdaki yorumlarda belirtildiği gibi, bu yaklaşım aslında yaklaşık olarak eşit uzunlukta sabit sayıda bölüm isteyen orijinal soruyu ele almamaktadır. Bununla birlikte, şu şekilde adlandırarak orijinal soruyu çözmek için yaklaşımımı kullanmaya devam edebilirsiniz:
myEnum.Section(myEnum.Count() / number_of_sections + 1)
Bu şekilde kullanıldığında, Count () işlemi O (N) olduğundan yaklaşım artık O (1) değildir.
Bu, kabul edilen cevapla aynı, ancak çok daha basit bir temsil:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
int numOfParts)
{
int i = 0;
return items.GroupBy(x => i++ % numOfParts);
}
Yukarıdaki yöntem IEnumerable<T>
, eşit büyüklükte veya eşit boyutlara yakın N sayıda parçaya böler .
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
Yukarıdaki yöntem, bir IEnumerable<T>
istenen sabit boyutta parçalara ve toplam parça sayısı önemsizdir - bu soru bununla ilgili değildir.
İle ilgili sorun Split
Yöntemle , daha yavaş olmasının yanı sıra, çıktıyı, gruplamanın her bir pozisyon için i'inci N katı temelinde yapılacağı veya başka bir deyişle parçaları alamayacağınız anlamında karıştırmasıdır. orijinal sırayla.
Buradaki hemen hemen her yanıt ya düzeni korumaz ya da bölme ve bölme değil ya da açıkça yanlıştır. Daha hızlı olan, düzeni koruyan ancak biraz daha ayrıntılı olan şunu deneyin:
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);
}
Bir eşdeğer bir yöntem Partition
işlemi burada
Daha önce yayınladığım Partition işlevini oldukça sık kullanıyorum. Bununla ilgili tek kötü şey, tamamen akış olmamasıydı. Dizinizde birkaç öğeyle çalışırsanız bu bir sorun değildir. Sıralamada 100.000'den fazla elemanla çalışmaya başladığımda yeni bir çözüme ihtiyacım vardı.
Aşağıdaki çözüm çok daha karmaşıktır (ve daha fazla koddur!), Ancak çok verimlidir.
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;
}
}
}
Zevk almak!
İlginç konu. Split / Partition'ın bir akış sürümünü elde etmek için numaralayıcılar kullanılabilir ve uzantı yöntemlerini kullanarak numaralandırıcıdan elde edilen diziler kullanılabilir. Verim kullanarak zorunlu kodu işlevsel koda dönüştürmek gerçekten çok güçlü bir tekniktir.
İlk olarak, bir dizi öğeyi tembel bir sıraya dönüştüren bir numaralandırıcı uzantısı:
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;
}
}
Ve sonra bir diziyi bölümleyen numaralandırılabilir bir uzantı:
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);
}
}
Sonuç, çok basit bir koda dayanan son derece verimli, akışlı ve tembel bir uygulamadır.
Zevk almak!
Bunu kullanıyorum:
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));
}
Bu bellek verimlidir ve yürütmeyi olabildiğince geciktirir (parti başına) ve doğrusal O (n) zamanında çalışır
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;
}
}
Bu sorunun (ve kuzenlerinin) birçok harika cevabı var. Buna kendim ihtiyacım vardı ve kaynak koleksiyonunun bir liste olarak değerlendirilebileceği bir senaryoda verimli ve hataya toleranslı olacak şekilde tasarlanmış bir çözüm yaratmıştım. Herhangi bir tembel yineleme kullanmaz, bu nedenle bellek baskısı uygulayabilecek bilinmeyen boyuttaki koleksiyonlar için uygun olmayabilir.
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 ve Math.Min kullanan bu soru ailesinde birkaç yanıt gördüm. Ancak genel olarak bunun hata kontrolü ve verimlilik açısından daha eksiksiz bir çözüm olduğuna inanıyorum.
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;
}
Harika Cevaplar, senaryom için kabul edilen cevabı test ettim ve görünüşe göre düzeni sağlamıyor. Nawfal'ın da düzeni sağlayan müthiş bir cevabı var. Ama senaryomda kalanı normalleştirilmiş bir şekilde bölmek istedim, gördüğüm tüm cevaplar geri kalanı ya da başında ya da sonunda yaydı.
Cevabım ayrıca geri kalanın daha normal bir şekilde yayılmasını da alıyor.
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;
}
}
Bu bölümlerdeki sipariş çok önemli değilse, şunu deneyebilirsiniz:
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;
Ancak bunlar bazı nedenlerle IEnumerable <IEnumerable <int>> 'e dönüştürülemez ...
Bu benim kodum, güzel ve kısa.
<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
İpli gibi bir bölme arıyordum, bu nedenle tüm Liste bazı kurallara göre bölünmüş, sadece ilk bölüm değil, bu benim çözümüm
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));
}
İşte parça sayısı yerine parça sayısı için küçük bir ayar:
public static class MiscExctensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
{
return (
list
.Select((o, n) => new { o, n })
.GroupBy(g => (int)(g.n / nbItems))
.Select(g => g.Select(x => x.o))
);
}
}
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();
Bu konuya yeni rastladım ve buradaki çözümlerin çoğu, koleksiyonlara öğeler eklemeyi, her sayfayı geri vermeden önce etkili bir şekilde gerçekleştirmeyi içeriyor. Bu, iki nedenden dolayı kötüdür - birincisi, sayfalarınız büyükse, sayfayı doldurmak için bir bellek ek yükü vardır, ikinci olarak, bir sonrakine geçtiğinizde önceki kayıtları geçersiz kılan yineleyiciler vardır (örneğin, bir Veri Okuyucuyu bir numaralandırıcı yöntemine sararsanız) .
Bu çözüm, öğeleri geçici koleksiyonlarda önbelleğe alma gereksinimini ortadan kaldırmak için iki iç içe yerleştirilmiş numaralandırıcı yöntemi kullanır. Dış ve iç yineleyiciler aynı numaralandırıcıyı geçtiği için, zorunlu olarak aynı numaralandırıcıyı paylaşırlar, bu nedenle, geçerli sayfayı işlemeyi bitirene kadar dıştaki olanı ilerletmemek önemlidir. Bununla birlikte, mevcut sayfanın tamamını yinelememeye karar verirseniz, sonraki sayfaya geçtiğinizde bu çözüm otomatik olarak sayfa sınırına ileriye doğru yinelenecektir.
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; }
}
}