Adakah cara yang bagus untuk membagi koleksi n
beberapa bagian dengan LINQ? Belum tentu merata tentunya.
Yaitu, saya ingin membagi koleksi menjadi sub-koleksi, yang masing-masing berisi subset elemen, di mana koleksi terakhir bisa compang-camping.
Adakah cara yang bagus untuk membagi koleksi n
beberapa bagian dengan LINQ? Belum tentu merata tentunya.
Yaitu, saya ingin membagi koleksi menjadi sub-koleksi, yang masing-masing berisi subset elemen, di mana koleksi terakhir bisa compang-camping.
Jawaban:
Linq murni dan solusi paling sederhana adalah seperti yang ditunjukkan di bawah ini.
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()
tidak diperlukan, IGrouping <T> sudah menjadi IEnumerable <T>.
EDIT: Oke, sepertinya saya salah membaca pertanyaan. Saya membacanya sebagai "potongan dengan panjang n" daripada "potongan n". Doh! Mempertimbangkan untuk menghapus jawaban ...
(Jawaban asli)
Saya tidak percaya ada cara untuk mempartisi, meskipun saya bermaksud untuk menulis satu cara untuk menambahkan LINQ ke Objek. Marc Gravell memiliki implementasi di sini meskipun saya mungkin akan memodifikasinya untuk mengembalikan tampilan hanya-baca:
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
. Ini membutuhkan satu batch untuk berada di memori pada satu waktu, tapi itu saja.
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}
. Setelah dipecah hasilnya seperti dept1 = {1,3,5}
dan dept2 = { 2,4 }
dimana parts = 2
. Tetapi hasil yang saya butuhkan adalah dept1 = {1,2,3}
dandept2 = {4,5}
int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);
kemudian melakukan pembagian .GroupBy(x => x.index / columnLength)
. Satu downside adalah Count () menyebutkan daftar.
Oke, saya akan melempar topi saya ke atas ring. Keuntungan dari algoritme saya:
Kode:
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();
}
Seperti yang ditunjukkan pada komentar di bawah, pendekatan ini tidak benar-benar menjawab pertanyaan awal yang meminta sejumlah bagian tetap dengan panjang yang kira-kira sama. Meskipun demikian, Anda masih dapat menggunakan pendekatan saya untuk menyelesaikan pertanyaan asli dengan menyebutnya seperti ini:
myEnum.Section(myEnum.Count() / number_of_sections + 1)
Jika digunakan dengan cara ini, pendekatannya bukan lagi O (1) karena operasi Count () adalah O (N).
Ini sama dengan jawaban yang diterima, tetapi representasi yang jauh lebih sederhana:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
int numOfParts)
{
int i = 0;
return items.GroupBy(x => i++ % numOfParts);
}
Metode di atas membagi IEnumerable<T>
menjadi sejumlah N potongan dengan ukuran yang sama atau mendekati ukuran yang sama.
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
Metode di atas membagi IEnumerable<T>
menjadi potongan-potongan dengan ukuran tetap yang diinginkan dengan jumlah total potongan menjadi tidak penting - yang bukan pertanyaannya.
Masalah dengan Split
metode ini, selain lebih lambat, adalah ia mengacak keluaran dalam arti bahwa pengelompokan akan dilakukan berdasarkan kelipatan ke-i dari N untuk setiap posisi, atau dengan kata lain Anda tidak mendapatkan potongannya. dalam urutan aslinya.
Hampir setiap jawaban di sini tidak menjaga ketertiban, atau tentang mempartisi dan tidak memisahkan, atau jelas salah. Coba ini yang lebih cepat, menjaga ketertiban tetapi sedikit lebih bertele-tele:
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);
}
Metode yang setara untuk Partition
operasi di sini
Saya telah menggunakan fungsi Partisi yang saya posting sebelumnya cukup sering. Satu-satunya hal buruk tentang itu adalah tidak sepenuhnya streaming. Ini bukan masalah jika Anda bekerja dengan sedikit elemen dalam urutan Anda. Saya membutuhkan solusi baru ketika saya mulai bekerja dengan 100.000+ elemen dalam urutan saya.
Solusi berikut jauh lebih kompleks (dan lebih banyak kode!), Tetapi sangat efisien.
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;
}
}
}
Nikmati!
Benang yang menarik. Untuk mendapatkan versi streaming Split / Partition, seseorang dapat menggunakan enumerator dan urutan hasil dari enumerator menggunakan metode ekstensi. Mengubah kode imperatif menjadi kode fungsional menggunakan hasil adalah teknik yang sangat ampuh.
Pertama, ekstensi enumerator yang mengubah hitungan elemen menjadi urutan malas:
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;
}
}
Dan kemudian ekstensi yang dapat dihitung yang mempartisi urutan:
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);
}
}
Hasil akhirnya adalah implementasi yang sangat efisien, streaming, dan malas yang mengandalkan kode yang sangat sederhana.
Nikmati!
Saya menggunakan ini:
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));
}
Ini adalah efisiensi memori dan menunda eksekusi sebanyak mungkin (per batch) dan beroperasi dalam waktu linier 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;
}
}
Ada banyak jawaban bagus untuk pertanyaan ini (dan sepupunya). Saya membutuhkan ini sendiri dan telah membuat solusi yang dirancang agar efisien dan toleran terhadap kesalahan dalam skenario di mana kumpulan sumber dapat diperlakukan sebagai daftar. Itu tidak menggunakan iterasi malas sehingga mungkin tidak cocok untuk koleksi dengan ukuran yang tidak diketahui yang mungkin menerapkan tekanan memori.
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));
}
}
Saya telah melihat beberapa jawaban di seluruh keluarga pertanyaan ini yang menggunakan GetRange dan Math.Min. Tapi saya yakin secara keseluruhan ini adalah solusi yang lebih lengkap dalam hal pengecekan kesalahan dan efisiensi.
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;
}
Jawaban Hebat, untuk skenario saya, saya menguji jawaban yang diterima, dan tampaknya itu tidak menjaga ketertiban. Ada juga jawaban bagus dari Nawfal yang menjaga ketertiban. Tetapi dalam skenario saya, saya ingin membagi sisanya dengan cara yang dinormalisasi, semua jawaban yang saya lihat menyebarkan sisanya atau di awal atau di akhir.
Jawaban saya juga membuat sisanya menyebar dengan cara yang lebih normal.
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;
}
}
Jika urutan di bagian ini tidak terlalu penting, Anda dapat mencoba ini:
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;
Namun ini tidak dapat dilemparkan ke IEnumerable <IEnumerable <int>> karena alasan tertentu ...
Ini kode saya, bagus dan pendek.
<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
Saya mencari perpecahan seperti yang ada string, jadi seluruh Daftar dipecah menurut beberapa aturan, tidak hanya bagian pertama, ini adalah solusi saya
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));
}
Berikut ini sedikit perubahan untuk jumlah item, bukan jumlah bagian:
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();
Baru saja menemukan utas ini, dan sebagian besar solusi di sini melibatkan penambahan item ke koleksi, yang secara efektif mewujudkan setiap halaman sebelum mengembalikannya. Ini buruk karena dua alasan - pertama jika halaman Anda besar, ada overhead memori untuk mengisi halaman, kedua ada iterator yang membuat catatan sebelumnya tidak valid saat Anda melanjutkan ke yang berikutnya (misalnya jika Anda menggabungkan DataReader dalam metode enumerator) .
Solusi ini menggunakan dua metode enumerator bersarang untuk menghindari kebutuhan untuk menyimpan item ke dalam cache sementara. Karena iterator luar dan dalam melintasi enumerator yang sama, mereka selalu berbagi enumerator yang sama, jadi penting untuk tidak memajukan yang luar sampai Anda selesai memproses halaman saat ini. Meskipun demikian, jika Anda memutuskan untuk tidak mengulang sepanjang halaman saat ini, saat Anda pindah ke halaman berikutnya, solusi ini akan beralih maju ke batas halaman secara otomatis.
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; }
}
}