Linq में बैच बनाएँ


104

क्या कोई लिनक में एक निश्चित आकार के बैच बनाने का तरीका सुझा सकता है?

आदर्श रूप से मैं कुछ विन्यास योग्य राशि का हिस्सा बनाने में सक्षम होना चाहता हूं।

जवाबों:


116

आपको कोई कोड लिखने की आवश्यकता नहीं है। MoreLINQ बैच विधि का उपयोग करें , जो स्रोत अनुक्रम को आकार की बाल्टियों में बैचती है (MoreLINQ एक NuGet पैकेज के रूप में उपलब्ध है जिसे आप स्थापित कर सकते हैं):

int size = 10;
var batches = sequence.Batch(size);

जिसे इस प्रकार लागू किया गया है:

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
                  this IEnumerable<TSource> source, int size)
{
    TSource[] bucket = null;
    var count = 0;

    foreach (var item in source)
    {
        if (bucket == null)
            bucket = new TSource[size];

        bucket[count++] = item;
        if (count != size)
            continue;

        yield return bucket;

        bucket = null;
        count = 0;
    }

    if (bucket != null && count > 0)
        yield return bucket.Take(count).ToArray();
}

3
4 बाइट्स प्रति आइटम बहुत अच्छा प्रदर्शन करता है ? क्या आपके पास कुछ परीक्षण हैं जो बताते हैं कि बहुत मायने क्या हैं ? यदि आप लाखों आइटम मेमोरी में लोड कर रहे हैं, तो मैं ऐसा नहीं करूंगा। सर्वर-साइड पेजिंग का उपयोग करें
सेर्गेई बेरेज़ोवस्की

4
मेरा मतलब है कि आप को नाराज न करें, लेकिन ऐसे सरल उपाय हैं जो बिल्कुल भी नहीं होते हैं। इसके अलावा यह गैर-मौजूद तत्वों के लिए भी जगह आवंटित करेगा:Batch(new int[] { 1, 2 }, 1000000)
निक व्हेल

7
@NickWhaley, अच्छी तरह से सहमत हैं कि अतिरिक्त स्थान आवंटित किया जाएगा, लेकिन वास्तविक जीवन में आपके पास आमतौर पर सिर्फ विपरीत स्थिति होती है - 1000 वस्तुओं की सूची जो 50 के बैचों में जानी चाहिए:
सर्गेई बेरेज़ोवस्की

1
हां स्थिति आमतौर पर दूसरे तरीके से होनी चाहिए, लेकिन वास्तविक जीवन में, ये उपयोगकर्ता इनपुट हो सकते हैं।
निक व्हले

8
यह पूरी तरह से ठीक समाधान है। वास्तविक जीवन में आप: उपयोगकर्ता इनपुट को मान्य करते हैं, बैचों को आइटमों के पूरे संग्रह के रूप में मानते हैं (जो वैसे भी वस्तुओं को जमा करता है), और अक्सर समानांतर में बैचों की प्रक्रिया करता है (जो पुनरावृत्ति दृष्टिकोण द्वारा समर्थित नहीं है, और जब तक आपको पता नहीं है, तब तक आपको आश्चर्य होगा। कार्यान्वयन विवरण)।
माइकल पेटिटो

90
public static class MyExtensions
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
                                                       int maxItems)
    {
        return items.Select((item, inx) => new { item, inx })
                    .GroupBy(x => x.inx / maxItems)
                    .Select(g => g.Select(x => x.item));
    }
}

और उपयोग होगा:

List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foreach(var batch in list.Batch(3))
{
    Console.WriteLine(String.Join(",",batch));
}

उत्पादन:

0,1,2
3,4,5
6,7,8
9

मेरे लिए एकदम सही काम किया
FunMatters

16
एक बार GroupByगणना शुरू करने के बाद , क्या इसके स्रोत को पूरी तरह से गणना करने की आवश्यकता नहीं है? यह स्रोत का आलसी मूल्यांकन खो देता है और इस प्रकार, कुछ मामलों में, बैचिंग के लाभ के सभी!
एलिक

1
वाह, धन्यवाद, आपने मुझे पागलपन से बचाया। बहुत अच्छी तरह से काम करता है
रियान डी लैंग

3
जैसा कि @ErikE उल्लेख करता है, यह विधि पूरी तरह से अपने स्रोत को
एनुमरेट करती है

1
ऐसा करें - यह पूरी तरह से उचित है जब आपको प्रदर्शनकारी प्रसंस्करण के लिए चीजों के छोटे बैचों में चीजों के मौजूदा ब्लॉक को तोड़ने की आवश्यकता होती है। विकल्प लूप की तलाश में एक सकल है जहां आप मैन्युअल रूप से बैचों को तोड़ते हैं। और अभी भी पूरे स्रोत से गुजरते हैं।
स्टिंगजैक

31

यदि आप sequenceएक के रूप में परिभाषित के साथ शुरू करते हैं IEnumerable<T>, और आप जानते हैं कि यह कई बार सुरक्षित रूप से एनुमरेट किया जा सकता है (जैसे कि यह एक सरणी या सूची है), तो आप बस बैचों में तत्वों को संसाधित करने के लिए इस सरल पैटर्न का उपयोग कर सकते हैं:

while (sequence.Any())
{
    var batch = sequence.Take(10);
    sequence = sequence.Skip(10);

    // do whatever you need to do with each batch here
}

2
W / o ज्यादा कोड या बाहरी लाइब्रेरी की आवश्यकता के लिए अच्छा, सरल तरीका
DevHawk

5
@DevHawk: यह है। ध्यान दें, हालांकि, यह प्रदर्शन बड़े (r) संग्रहों पर तेजी से भुगतना होगा
रॉबटहॉं

28

उपरोक्त सभी बड़े बैच या कम मेमोरी स्पेस के साथ बहुत अच्छा प्रदर्शन करते हैं। अपनी खुद की लिखना होगा कि पाइपलाइन (कहीं भी कोई आइटम संचय नोटिस नहीं):

public static class BatchLinq {
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");

        using (IEnumerator<T> enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
                yield return TakeIEnumerator(enumerator, size);
    }

    private static IEnumerable<T> TakeIEnumerator<T>(IEnumerator<T> source, int size) {
        int i = 0;
        do
            yield return source.Current;
        while (++i < size && source.MoveNext());
    }
}

संपादित करें: इस दृष्टिकोण के साथ ज्ञात समस्या यह है कि प्रत्येक बैच को अगले बैच में जाने से पहले पूरी तरह से एन्यूमरेट और एनुमरेट किया जाना चाहिए। उदाहरण के लिए यह काम नहीं करता है:

//Select first item of every 100 items
Batch(list, 100).Select(b => b.First())

1
नियमित @LB ऊपर पोस्ट किया गया है या तो आइटम संचय प्रदर्शन नहीं करता है।
नेणतापिर जूल

2
@neontapir अभी भी करता है। एक सिक्का छांटने वाली मशीन जो आपको पहले निकल देती है, फिर मंद होती है, आपको पहले हर एक सिक्के का निरीक्षण करना चाहिए ताकि आप यह सुनिश्चित कर सकें कि कोई और निकल नहीं है।
निक व्हले

2
जब मैंने इस कोड को छीन लिया, तो अहह आह, आपका संपादन नोट याद आ गया। यह समझने में कुछ समय लगा कि क्यों गैर-एन्यूमरेटेड बैचों पर पुनरावृत्ति वास्तव में पूरे मूल संग्रह (!!!) की गणना करती है, एक्स बैच प्रदान करते हुए, प्रत्येक में 1 आइटम (जहां एक्स मूल संग्रह आइटमों की संख्या है) की गणना की गई है।
एली

2
@NickWhaley अगर मैं IEnumerable <IEnumerable <T >> पर अपने कोड के द्वारा काउंट () करता हूं, तो यह गलत उत्तर देता है, यह कुल तत्वों की संख्या देता है, जब अपेक्षित कुल बैचों की संख्या होती है। मोरलिनक बैच कोड के साथ ऐसा नहीं है
मृणाल कंबोज

1
: - @JohnZabroski यहां एक त्वरित सार है gist.github.com/mmurrell/9225ed7c4d107c2195057f77e07f0f68
मैट Murrell

24

यह एक पूरी तरह से आलसी, कम ओवरहेड, बैच का एक-फंक्शन कार्यान्वयन है जो कोई संचय नहीं करता है। EricRoller की मदद से Nick Whaley के समाधान पर आधारित (और समस्याओं को हल करता है) ।

पुनरावृत्ति अंतर्निहित IEnumerable से सीधे आती है, इसलिए तत्वों को सख्त क्रम में गणना की जानी चाहिए, और एक से अधिक बार एक्सेस नहीं किया जाना चाहिए। यदि कुछ तत्वों का आंतरिक लूप में उपभोग नहीं किया जाता है, तो उन्हें छोड़ दिया जाता है (और एक पुनरावृत्त चलने वाले के माध्यम से उन्हें फिर से एक्सेस करने की कोशिश कर रहा है InvalidOperationException: Enumeration already finished.)।

आप .NET फिडल पर एक पूर्ण नमूने का परीक्षण कर सकते हैं ।

public static class BatchLinq
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");
        using (var enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
            {
                int i = 0;
                // Batch is a local function closing over `i` and `enumerator` that
                // executes the inner batch enumeration
                IEnumerable<T> Batch()
                {
                    do yield return enumerator.Current;
                    while (++i < size && enumerator.MoveNext());
                }

                yield return Batch();
                while (++i < size && enumerator.MoveNext()); // discard skipped items
            }
    }
}

2
यह एकमात्र पूर्ण रूप से आलसी कार्यान्वयन है। अजगर itertools.GroupBy कार्यान्वयन के अनुरूप।
एरिक रोलर

1
आप doneहमेशा के e.Count()बाद कॉल करके चेक को समाप्त कर सकते हैं yield return e। आप अपरिभाषित व्यवहार आह्वान नहीं करने के लिए BatchInner में पाश को पुनर्व्यवस्थित करने की आवश्यकता होगी source.Current, तो i >= size। यह BatchInnerप्रत्येक बैच के लिए एक नया आवंटित करने की आवश्यकता को समाप्त कर देगा ।
एरिक रोलर

1
आप सही हैं, फिर भी आपको प्रत्येक बैच की प्रगति के बारे में जानकारी हासिल करनी होगी। यदि आप प्रत्येक बैच से 2 आइटम प्राप्त करने का प्रयास करते हैं तो मुझे आपके कोड में एक बग मिला: बग फिडेल । एक अलग वर्ग के बिना फिक्स्ड कार्यान्वयन (सी # 7 का उपयोग करके) यहां है: फिक्स्ड फिडेल । ध्यान दें कि मुझे उम्मीद है कि CLR अभी भी चर को पकड़ने के लिए लूप प्रति एक बार स्थानीय फंक्शन बनाएगा, iइसलिए यह एक अलग वर्ग को परिभाषित करने की तुलना में अधिक कुशल नहीं है, लेकिन मुझे लगता है कि यह थोड़ा क्लीनर है।
एरिक रोलर

1
मैंने इस संस्करण को बेंचमार्कडॉटनेट का उपयोग करके System.eactive.Linq.EnumerableEx.Buffer के खिलाफ बेंचमार्क किया और आपका कार्यान्वयन सुरक्षा के जोखिम पर 3-4 तेजी से हुआ। आंतरिक रूप से, EnumerableEx.Buffer एक सूची आवंटित करता है <T> github.com/dotnet/reactive/blob/…
John Zabroski

1
यदि आप इसका एक बफ़र्ड संस्करण चाहते हैं, तो आप कर सकते हैं: सार्वजनिक स्थैतिक IEnumerable <IReadOnlyList <T >> बैचबैफ़र्ड <T> (यह IEnumerable <T> स्रोत, int size) => बैच (स्रोत, आकार) .Select (chunk =)। > (IReadOnlyList <T>) chunk.ToList ()); IReadOnlyList <T> का उपयोग उपयोगकर्ता को संकेत देने के लिए है कि आउटपुट कैश्ड है। आप इसके बजाय IEnumerable <IEnumerable <T >> भी रख सकते हैं।
gfache

11

मुझे आश्चर्य है कि क्यों किसी ने कभी भी एक पुराने स्कूल के लिए लूप समाधान पोस्ट नहीं किया है। यहाँ एक है:

List<int> source = Enumerable.Range(1,23).ToList();
int batchsize = 10;
for (int i = 0; i < source.Count; i+= batchsize)
{
    var batch = source.Skip(i).Take(batchsize);
}

यह सादगी संभव है क्योंकि टेक विधि:

... sourceतत्वों की पैदावार होने तक countतत्वों की गणना और पैदावार करता है या sourceइसमें अधिक तत्व नहीं होते हैं। यदि countतत्वों की संख्या से अधिक है source, तो सभी तत्व sourceवापस आ जाते हैं

अस्वीकरण:

लूप के अंदर स्किप और टेक का उपयोग करने का मतलब है कि एन्यूमरेबल को कई बार एन्यूमरेट किया जाएगा। यह खतरनाक है अगर गणना करने योग्य को स्थगित कर दिया जाता है। इसके परिणामस्वरूप डेटाबेस क्वेरी, या वेब अनुरोध या फ़ाइल पढ़ने के कई निष्पादन हो सकते हैं। यह उदाहरण स्पष्ट रूप से एक सूची के उपयोग के लिए है जिसे स्थगित नहीं किया गया है, इसलिए यह किसी समस्या से कम नहीं है। यह अभी भी एक धीमी गति से समाधान है क्योंकि स्किप हर बार जब यह कहा जाता है तो संग्रह को फिर से जोड़ देगा।

यह GetRangeविधि का उपयोग करके भी हल किया जा सकता है , लेकिन इसे एक संभावित रेस्ट बैच निकालने के लिए एक अतिरिक्त गणना की आवश्यकता होती है:

for (int i = 0; i < source.Count; i += batchsize)
{
    int remaining = source.Count - i;
    var batch = remaining > batchsize  ? source.GetRange(i, batchsize) : source.GetRange(i, remaining);
}

यहां इसे संभालने का तीसरा तरीका है, जो 2 छोरों के साथ काम करता है। यह सुनिश्चित करता है कि संग्रह केवल 1 बार ही एन्यूमरेट किया गया हो! "

int batchsize = 10;
List<int> batch = new List<int>(batchsize);

for (int i = 0; i < source.Count; i += batchsize)
{
    // calculated the remaining items to avoid an OutOfRangeException
    batchsize = source.Count - i > batchsize ? batchsize : source.Count - i;
    for (int j = i; j < i + batchsize; j++)
    {
        batch.Add(source[j]);
    }           
    batch.Clear();
}

2
बहुत अच्छा समाधान। लोग भूल गए कि लूप के लिए कैसे उपयोग किया जाए
विटालिक्स

1
लूप के उपयोग Skipऔर Takeअंदर का मतलब है कि एन्यूमरेबल को कई बार एन्यूमरेट किया जाएगा। यह खतरनाक है अगर गणना करने योग्य को स्थगित कर दिया जाता है। इसके परिणामस्वरूप डेटाबेस क्वेरी, या वेब अनुरोध या फ़ाइल पढ़ने के कई निष्पादन हो सकते हैं। आपके उदाहरण में आपके पास एक है Listजो स्थगित नहीं है, इसलिए यह एक समस्या से कम नहीं है।
थियोडोर जूलियास

@ TheodorZoulias हाँ मुझे पता है, यह वास्तव में है क्योंकि मैंने आज दूसरा समाधान पोस्ट किया है। मैंने आपकी टिप्पणी को अस्वीकरण के रूप में पोस्ट किया, क्योंकि आपने इसे काफी अच्छी तरह से तैयार किया है, क्या मैं आपको उद्धृत करूंगा?
मोंग झू

मैंने 2 छोरों के साथ एक तीसरा समाधान लिखा ताकि संग्रह केवल 1 बार गणना की जाए। Skip.take बात एक बहुत ही अकुशल समाधान है
मोंग झू

4

MoreLINQ के रूप में एक ही दृष्टिकोण, लेकिन सरणी के बजाय सूची का उपयोग करना। मैंने बेंचमार्किंग नहीं की है, लेकिन कुछ लोगों के लिए पठनीयता अधिक मायने रखती है:

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        List<T> batch = new List<T>();

        foreach (var item in source)
        {
            batch.Add(item);

            if (batch.Count >= size)
            {
                yield return batch;
                batch.Clear();
            }
        }

        if (batch.Count > 0)
        {
            yield return batch;
        }
    }

1
आपको बैच चर का पुन: उपयोग नहीं करना चाहिए। आपके उपभोक्ताओं को इसके द्वारा पूरी तरह से खराब किया जा सकता है। इसके अलावा, इसके आकार को अनुकूलित sizeकरने के लिए पैरामीटर में पास करें new List
ErikE

1
आराम से ठीक: की जगह batch.Clear();के साथbatch = new List<T>();
NetMage

3

यहाँ Nick Whaley's ( लिंक ) और infogulch's ( लिंक ) आलसी Batchकार्यान्वयन का एक बेहतर सुधार है । यह सख्त है। आप या तो सही क्रम में बैचों की गणना करते हैं, या आपको अपवाद मिलता है।

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
    this IEnumerable<TSource> source, int size)
{
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
    using (var enumerator = source.GetEnumerator())
    {
        int i = 0;
        while (enumerator.MoveNext())
        {
            if (i % size != 0) throw new InvalidOperationException(
                "The enumeration is out of order.");
            i++;
            yield return GetBatch();
        }
        IEnumerable<TSource> GetBatch()
        {
            while (true)
            {
                yield return enumerator.Current;
                if (i % size == 0 || !enumerator.MoveNext()) break;
                i++;
            }
        }
    }
}

और यहाँ Batchप्रकार के स्रोतों के लिए एक आलसी कार्यान्वयन है IList<T>। यह गणना पर कोई प्रतिबंध नहीं लगाता है। बैचों को किसी भी क्रम में, और एक से अधिक बार आंशिक रूप से गणना की जा सकती है। गणना के दौरान संग्रह को संशोधित नहीं करने का प्रतिबंध अभी भी लागू है। यह enumerator.MoveNext()किसी भी चंक या तत्व की उपज से पहले डमी कॉल करके हासिल किया जाता है । नकारात्मक पक्ष यह है कि प्रगणक को अपरिष्कृत छोड़ दिया जाता है, क्योंकि यह अज्ञात है जब संलयन समाप्त होने जा रहा है।

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
    this IList<TSource> source, int size)
{
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
    var enumerator = source.GetEnumerator();
    for (int i = 0; i < source.Count; i += size)
    {
        enumerator.MoveNext();
        yield return GetChunk(i, Math.Min(i + size, source.Count));
    }
    IEnumerable<TSource> GetChunk(int from, int toExclusive)
    {
        for (int j = from; j < toExclusive; j++)
        {
            enumerator.MoveNext();
            yield return source[j];
        }
    }
}

2

मैं इसमें बहुत देर से शामिल हो रहा हूं लेकिन मुझे कुछ और दिलचस्प लगा।

इसलिए हम यहां Skipऔर Takeबेहतर प्रदर्शन के लिए उपयोग कर सकते हैं ।

public static class MyExtensions
    {
        public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
        {
            return items.Select((item, index) => new { item, index })
                        .GroupBy(x => x.index / maxItems)
                        .Select(g => g.Select(x => x.item));
        }

        public static IEnumerable<T> Batch2<T>(this IEnumerable<T> items, int skip, int take)
        {
            return items.Skip(skip).Take(take);
        }

    }

आगे मैंने 100000 रिकॉर्ड के साथ जाँच की। लूपिंग केवल के मामले में अधिक समय ले रहा हैBatch

कंसोल एप्लिकेशन का कोड।

static void Main(string[] args)
{
    List<string> Ids = GetData("First");
    List<string> Ids2 = GetData("tsriF");

    Stopwatch FirstWatch = new Stopwatch();
    FirstWatch.Start();
    foreach (var batch in Ids2.Batch(5000))
    {
        // Console.WriteLine("Batch Ouput:= " + string.Join(",", batch));
    }
    FirstWatch.Stop();
    Console.WriteLine("Done Processing time taken:= "+ FirstWatch.Elapsed.ToString());


    Stopwatch Second = new Stopwatch();

    Second.Start();
    int Length = Ids2.Count;
    int StartIndex = 0;
    int BatchSize = 5000;
    while (Length > 0)
    {
        var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
        // Console.WriteLine("Second Batch Ouput:= " + string.Join(",", SecBatch));
        Length = Length - BatchSize;
        StartIndex += BatchSize;
    }

    Second.Stop();
    Console.WriteLine("Done Processing time taken Second:= " + Second.Elapsed.ToString());
    Console.ReadKey();
}

static List<string> GetData(string name)
{
    List<string> Data = new List<string>();
    for (int i = 0; i < 100000; i++)
    {
        Data.Add(string.Format("{0} {1}", name, i.ToString()));
    }

    return Data;
}

समय ऐसा हो गया।

पहला - 00: 00: 00.0708, 00: 00: 00.0660

दूसरा (टेक एंड स्किप वन) - 00: 00: 00.0008, 00: 00: 00.0008


1
GroupByपूरी तरह से इससे पहले कि यह एक पंक्ति पैदा करता है enumerates। यह बैचिंग करने का अच्छा तरीका नहीं है।
एरिक

@ एप्रिक यह इस बात पर निर्भर करता है कि आप क्या हासिल करना चाहते हैं। यदि बैचिंग समस्या नहीं है, और आपको बस आइटम को छोटे टुकड़ों में विभाजित करने की आवश्यकता है, तो प्रसंस्करण के लिए यह सिर्फ बात हो सकती है। मैं इसका उपयोग MSCRM के लिए कर रहा हूँ जहाँ 100 रिकॉर्ड हो सकते हैं जो LAMBDA को
बैचने के

1
ज़रूर, ऐसे मामले हैं जहाँ पूर्ण गणना कोई मायने नहीं रखती है। लेकिन जब आप एक शानदार लिख सकते हैं तो दूसरी श्रेणी की उपयोगिता पद्धति क्यों लिखें?
एरिक

अच्छा विकल्प है लेकिन समान नहीं है क्योंकि पहले सूचियों की एक सूची देता है जो आपको लूप के माध्यम से अनुमति देता है।
गारेथ हॉपकिंस

समयबद्ध परिणामों foreach (var batch in Ids2.Batch(5000))को बदलना var gourpBatch = Ids2.Batch(5000)और जांचना। var SecBatch = Ids2.Batch2(StartIndex, BatchSize);अगर समय में बदलाव के लिए आपके परिणाम हैं, तो मुझे इसमें दिलचस्पी लेनी होगी।
सीबकिट

2

तो एक कार्यात्मक टोपी के साथ, यह तुच्छ प्रतीत होता है .... लेकिन सी # में, कुछ महत्वपूर्ण डाउनसाइड हैं।

आप शायद इसे IEnumerable (इसे Google के रूप में प्रकट करेंगे और आप संभवतः कुछ हास्केल डॉक्स में समाप्त कर देंगे, लेकिन हो सकता है कि कुछ # F # सामान का उपयोग किया जाए, यदि आप F # जानते हैं, तो Haskell डॉक्स पर स्क्विंट और यह बना देगा समझ)।

अनफोल्ड का संबंध आईईएन्यूमेरेरेबल के माध्यम से पुनरावृत्ति करने के बजाय फोल्ड ("एग्रीगेट") से है, यह आउटपुट डेटा स्ट्रक्चर्स के माध्यम से पुनरावृत्ति करता है (इसके आईएनम्यूएरेबल और आईओब्सर्वेबल के बीच एक समान संबंध है, वास्तव में मुझे लगता है कि आईओब्जर्वेबल एक "अनफोल्ड" जेनरेट करता है)। ..)

वैसे भी पहले आपको एक अनकही विधि की आवश्यकता है, मुझे लगता है कि यह काम करता है (दुर्भाग्य से यह अंततः बड़ी "सूचियों" के लिए स्टैक को उड़ा देगा ... आप इसे सुरक्षित रूप से एफ # उपज का उपयोग करके लिख सकते हैं!

    static IEnumerable<T> Unfold<T, U>(Func<U, IEnumerable<Tuple<U, T>>> f, U seed)
    {
        var maybeNewSeedAndElement = f(seed);

        return maybeNewSeedAndElement.SelectMany(x => new[] { x.Item2 }.Concat(Unfold(f, x.Item1)));
    }

यह थोड़ा विरोधाभास है क्योंकि C # उन कुछ चीज़ों को लागू नहीं करता है जिन्हें कार्यात्मक लैंसग्यूज़ ने अनुमति दी है ... लेकिन यह मूल रूप से एक बीज लेता है और फिर IEnumerable में अगले तत्व का एक "हो सकता है" उत्तर और अगला बीज (हो सकता है) C # में मौजूद नहीं है, इसलिए हमने IEnumerable का उपयोग इसे नकली करने के लिए किया है), और शेष उत्तर को संक्षिप्त करता है (मैं इस "O (n?)" की जटिलता के लिए वाउच नहीं कर सकता)।

एक बार जब आप ऐसा कर लेते हैं;

    static IEnumerable<IEnumerable<T>> Batch<T>(IEnumerable<T> xs, int n)
    {
        return Unfold(ys =>
            {
                var head = ys.Take(n);
                var tail = ys.Skip(n);
                return head.Take(1).Select(_ => Tuple.Create(tail, head));
            },
            xs);
    }

यह सब काफी साफ दिखता है ... आप "n" तत्वों को IEnumerable में "अगले" तत्व के रूप में लेते हैं, और "पूंछ" बाकी अप्रमाणित सूची में है।

अगर सिर में कुछ नहीं है ... आप खत्म हो चुके हैं ... आप "कुछ भी नहीं" (लेकिन एक खाली IEnumerable> के रूप में नकली) वापस करते हैं ... अन्यथा आप प्रक्रिया करने के लिए सिर तत्व और पूंछ वापस करते हैं।

आप शायद IObservable का उपयोग करके ऐसा कर सकते हैं, वहाँ शायद "बैच" विधि पहले से ही है, और आप शायद इसका उपयोग कर सकते हैं।

यदि स्टैक ओवरफ्लो का खतरा बढ़ जाता है (यह शायद चाहिए), तो आपको एफ # में लागू करना चाहिए (और शायद इसके साथ कुछ एफ # लाइब्रेरी (एफएसएचआरपीएक्स?) पहले से ही है)।

(मैंने केवल इसके कुछ अल्पविकसित परीक्षण किए हैं, इसलिए इसमें अजीब तरह के कीड़े हो सकते हैं)।


1

मैंने एक कस्टम IEnumerable कार्यान्वयन लिखा है जो linq के बिना काम करता है और डेटा पर एकल गणना की गारंटी देता है। यह उन सभी सूचियों या सरणियों की आवश्यकता के बिना भी पूरा करता है जो बड़े डेटा सेट पर मेमोरी विस्फोट का कारण बनते हैं।

यहाँ कुछ बुनियादी परीक्षण दिए गए हैं:

    [Fact]
    public void ShouldPartition()
    {
        var ints = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        var data = ints.PartitionByMaxGroupSize(3);
        data.Count().Should().Be(4);

        data.Skip(0).First().Count().Should().Be(3);
        data.Skip(0).First().ToList()[0].Should().Be(0);
        data.Skip(0).First().ToList()[1].Should().Be(1);
        data.Skip(0).First().ToList()[2].Should().Be(2);

        data.Skip(1).First().Count().Should().Be(3);
        data.Skip(1).First().ToList()[0].Should().Be(3);
        data.Skip(1).First().ToList()[1].Should().Be(4);
        data.Skip(1).First().ToList()[2].Should().Be(5);

        data.Skip(2).First().Count().Should().Be(3);
        data.Skip(2).First().ToList()[0].Should().Be(6);
        data.Skip(2).First().ToList()[1].Should().Be(7);
        data.Skip(2).First().ToList()[2].Should().Be(8);

        data.Skip(3).First().Count().Should().Be(1);
        data.Skip(3).First().ToList()[0].Should().Be(9);
    }

डेटा को विभाजित करने के लिए एक्सटेंशन विधि।

/// <summary>
/// A set of extension methods for <see cref="IEnumerable{T}"/>. 
/// </summary>
public static class EnumerableExtender
{
    /// <summary>
    /// Splits an enumerable into chucks, by a maximum group size.
    /// </summary>
    /// <param name="source">The source to split</param>
    /// <param name="maxSize">The maximum number of items per group.</param>
    /// <typeparam name="T">The type of item to split</typeparam>
    /// <returns>A list of lists of the original items.</returns>
    public static IEnumerable<IEnumerable<T>> PartitionByMaxGroupSize<T>(this IEnumerable<T> source, int maxSize)
    {
        return new SplittingEnumerable<T>(source, maxSize);
    }
}

यह कार्यान्वयन वर्ग है

    using System.Collections;
    using System.Collections.Generic;

    internal class SplittingEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        private readonly IEnumerable<T> backing;
        private readonly int maxSize;
        private bool hasCurrent;
        private T lastItem;

        public SplittingEnumerable(IEnumerable<T> backing, int maxSize)
        {
            this.backing = backing;
            this.maxSize = maxSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new Enumerator(this, this.backing.GetEnumerator());
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private class Enumerator : IEnumerator<IEnumerable<T>>
        {
            private readonly SplittingEnumerable<T> parent;
            private readonly IEnumerator<T> backingEnumerator;
            private NextEnumerable current;

            public Enumerator(SplittingEnumerable<T> parent, IEnumerator<T> backingEnumerator)
            {
                this.parent = parent;
                this.backingEnumerator = backingEnumerator;
                this.parent.hasCurrent = this.backingEnumerator.MoveNext();
                if (this.parent.hasCurrent)
                {
                    this.parent.lastItem = this.backingEnumerator.Current;
                }
            }

            public bool MoveNext()
            {
                if (this.current == null)
                {
                    this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                    return true;
                }
                else
                {
                    if (!this.current.IsComplete)
                    {
                        using (var enumerator = this.current.GetEnumerator())
                        {
                            while (enumerator.MoveNext())
                            {
                            }
                        }
                    }
                }

                if (!this.parent.hasCurrent)
                {
                    return false;
                }

                this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                return true;
            }

            public void Reset()
            {
                throw new System.NotImplementedException();
            }

            public IEnumerable<T> Current
            {
                get { return this.current; }
            }

            object IEnumerator.Current
            {
                get { return this.Current; }
            }

            public void Dispose()
            {
            }
        }

        private class NextEnumerable : IEnumerable<T>
        {
            private readonly SplittingEnumerable<T> splitter;
            private readonly IEnumerator<T> backingEnumerator;
            private int currentSize;

            public NextEnumerable(SplittingEnumerable<T> splitter, IEnumerator<T> backingEnumerator)
            {
                this.splitter = splitter;
                this.backingEnumerator = backingEnumerator;
            }

            public bool IsComplete { get; private set; }

            public IEnumerator<T> GetEnumerator()
            {
                return new NextEnumerator(this.splitter, this, this.backingEnumerator);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            private class NextEnumerator : IEnumerator<T>
            {
                private readonly SplittingEnumerable<T> splitter;
                private readonly NextEnumerable parent;
                private readonly IEnumerator<T> enumerator;
                private T currentItem;

                public NextEnumerator(SplittingEnumerable<T> splitter, NextEnumerable parent, IEnumerator<T> enumerator)
                {
                    this.splitter = splitter;
                    this.parent = parent;
                    this.enumerator = enumerator;
                }

                public bool MoveNext()
                {
                    this.parent.currentSize += 1;
                    this.currentItem = this.splitter.lastItem;
                    var hasCcurent = this.splitter.hasCurrent;

                    this.parent.IsComplete = this.parent.currentSize > this.splitter.maxSize;

                    if (this.parent.IsComplete)
                    {
                        return false;
                    }

                    if (hasCcurent)
                    {
                        var result = this.enumerator.MoveNext();

                        this.splitter.lastItem = this.enumerator.Current;
                        this.splitter.hasCurrent = result;
                    }

                    return hasCcurent;
                }

                public void Reset()
                {
                    throw new System.NotImplementedException();
                }

                public T Current
                {
                    get { return this.currentItem; }
                }

                object IEnumerator.Current
                {
                    get { return this.Current; }
                }

                public void Dispose()
                {
                }
            }
        }
    }

1

मुझे पता है कि हर कोई इस काम को करने के लिए जटिल प्रणालियों का उपयोग करता है, और मुझे वास्तव में ऐसा क्यों नहीं मिलता है। टेक एंड स्किप आम Func<TSource,Int32,TResult>फंक्शन वाले ट्रांसफॉर्म फंक्शन के इस्तेमाल से उन सभी ऑपरेशंस की अनुमति देगा । पसंद:

public IEnumerable<IEnumerable<T>> Buffer<T>(IEnumerable<T> source, int size)=>
    source.Select((item, index) => source.Skip(size * index).Take(size)).TakeWhile(bucket => bucket.Any());

2
यह बहुत अक्षम हो सकता है, क्योंकि दिए गए sourceबहुत बार पुनरावृत्त होंगे।
केविन मेयर

1
यह न केवल अक्षम है, बल्कि गलत परिणाम भी दे सकता है। इस बात की कोई गारंटी नहीं है कि किसी एन्यूमरेबल को दो बार एन्यूमरेट करने पर समान तत्व मिलेंगे। एक उदाहरण के रूप में इस enumerable ले लो Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next())):।
थियोडोर जूलियास

1

बस एक और एक लाइन कार्यान्वयन। यह एक खाली सूची के साथ भी काम करता है, इस मामले में आपको एक शून्य आकार का बैच संग्रह मिलता है।

var aList = Enumerable.Range(1, 100).ToList(); //a given list
var size = 9; //the wanted batch size
//number of batches are: (aList.Count() + size - 1) / size;

var batches = Enumerable.Range(0, (aList.Count() + size - 1) / size).Select(i => aList.GetRange( i * size, Math.Min(size, aList.Count() - i * size)));

Assert.True(batches.Count() == 12);
Assert.AreEqual(batches.ToList().ElementAt(0), new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Assert.AreEqual(batches.ToList().ElementAt(1), new List<int>() { 10, 11, 12, 13, 14, 15, 16, 17, 18 });
Assert.AreEqual(batches.ToList().ElementAt(11), new List<int>() { 100 });

1

दूसरा तरीका Rx बफर ऑपरेटर का उपयोग कर रहा है

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();

आपको कभी भी उपयोग नहीं करना चाहिए GetAwaiter().GetResult()। यह सिंक्रोनस कोड के लिए एक कोड गंध है जिसे बलपूर्वक async कोड कहते हैं।
gfache

-2
    static IEnumerable<IEnumerable<T>> TakeBatch<T>(IEnumerable<T> ts,int batchSize)
    {
        return from @group in ts.Select((x, i) => new { x, i }).ToLookup(xi => xi.i / batchSize)
               select @group.Select(xi => xi.x);
    }

अपने उत्तर में कुछ विवरण / पाठ जोड़ें। केवल कोड डालना अधिकांश समय कम अर्थ हो सकता है।
अरफुल हक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.