ใช้ Linq เพื่อรับองค์ประกอบ N สุดท้ายของคอลเล็กชันหรือไม่


284

รับชุดมีวิธีการรับองค์ประกอบ N สุดท้ายของคอลเลกชันที่? หากไม่มีวิธีใดในกรอบสิ่งที่จะเป็นวิธีที่ดีที่สุดในการเขียนวิธีการขยายให้ทำเช่นนี้?

คำตอบ:


422
collection.Skip(Math.Max(0, collection.Count() - N));

วิธีนี้จะรักษาลำดับของรายการโดยไม่ต้องพึ่งพาการเรียงลำดับใด ๆ และมีความเข้ากันได้ในวงกว้างในผู้ให้บริการ LINQ หลายราย

เป็นสิ่งสำคัญที่จะต้องระวังไม่ให้โทรออกSkipด้วยหมายเลขติดลบ ผู้ให้บริการบางรายเช่น Entity Framework จะสร้าง ArgumentException เมื่อมีข้อโต้แย้งเชิงลบ การเรียกร้องเพื่อMath.Maxหลีกเลี่ยงปัญหานี้อย่างเป็นระเบียบ

คลาสด้านล่างมีสิ่งจำเป็นทั้งหมดสำหรับวิธีการขยายซึ่ง ได้แก่ : คลาสแบบคงที่วิธีแบบคงที่และการใช้thisคำหลัก

public static class MiscExtensions
{
    // Ex: collection.TakeLast(5);
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
    {
        return source.Skip(Math.Max(0, source.Count() - N));
    }
}

หมายเหตุสั้น ๆ เกี่ยวกับประสิทธิภาพ:

เนื่องจากการเรียกใช้Count()สามารถทำให้เกิดการแจงนับของโครงสร้างข้อมูลบางอย่างวิธีการนี้มีความเสี่ยงที่ทำให้เกิดการส่งผ่านข้อมูลสองครั้ง นี่ไม่ใช่ปัญหาจริง ๆ กับการแจกแจงส่วนใหญ่ อันที่จริงแล้วการปรับให้เหมาะสมมีอยู่แล้วสำหรับรายการ, อาร์เรย์และแม้กระทั่งแบบสอบถามของ EF เพื่อประเมินการCount()ดำเนินการในเวลา O (1)

อย่างไรก็ตามหากคุณต้องใช้การนับล่วงหน้าเท่านั้นและต้องการหลีกเลี่ยงการทำสองรอบให้พิจารณาอัลกอริทึม One-Pass เช่นLasse V. KarlsenหรือMark Byersอธิบาย ทั้งสองวิธีใช้บัฟเฟอร์ชั่วคราวเพื่อเก็บรายการในขณะที่การแจกแจงซึ่งจะให้ผลเมื่อพบจุดสิ้นสุดของการรวบรวม


2
+1 เนื่องจากสามารถใช้งานได้ใน Linq ถึง Entities / SQL ฉันเดาว่ามันเป็นนักแสดงใน Linq สำหรับ Objects มากกว่ากลยุทธ์ของ James Curran
StriplingWarrior

11
ขึ้นอยู่กับลักษณะของการสะสม จำนวน () อาจเป็น O (N)
James Curran

3
@James: ถูกต้องอย่างแน่นอน หากจัดการกับคอลเลกชัน IEnumerable อย่างเคร่งครัดนี่อาจเป็นคิวรีแบบสองรอบ ฉันสนใจที่จะเห็นอัลกอริทึมแบบ 1 รอบที่รับประกัน มันอาจจะมีประโยชน์
kbrimington

4
ทำเกณฑ์มาตรฐานบางอย่าง ปรากฎว่า LINQ เป็นวัตถุทำการปรับให้เหมาะสมบางอย่างขึ้นอยู่กับประเภทของคอลเลกชันที่คุณใช้ การใช้อาร์เรย์Lists และLinkedLists การแก้ปัญหาของเจมส์มีแนวโน้มที่จะเร็วขึ้นแม้ว่าจะไม่ใช่ลำดับความสำคัญก็ตาม หากคำนวณ IEnumerable ได้ (ผ่าน Enumerable.Range เป็นต้น) โซลูชันของ James ใช้เวลานานกว่า ฉันไม่สามารถคิดวิธีใด ๆ ที่จะรับประกันการผ่านครั้งเดียวโดยไม่ทราบว่าจะมีการนำไปใช้หรือคัดลอกค่าไปยังโครงสร้างข้อมูลอื่น
StriplingWarrior

1
@RedFilter - ยุติธรรมเพียงพอ ฉันคิดว่านิสัยเพจเพจรั่วไหลออกมาที่นี่ ขอบคุณสำหรับสายตาที่กระตือรือร้นของคุณ
kbrimington

59
coll.Reverse().Take(N).Reverse().ToList();


public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> coll, int N)
{
    return coll.Reverse().Take(N).Reverse();
}

อัปเดต: เพื่อแก้ไขปัญหาของ clintp: a) การใช้วิธี TakeLast () ที่ฉันกำหนดไว้ด้านบนแก้ปัญหาได้ แต่ถ้าคุณต้องการทำโดยไม่มีวิธีพิเศษคุณก็ต้องจำไว้ว่าในขณะที่ Enumerable () สามารถ ใช้เป็นวิธีส่วนขยายคุณไม่จำเป็นต้องใช้วิธีดังกล่าว:

List<string> mystring = new List<string>() { "one", "two", "three" }; 
mystring = Enumerable.Reverse(mystring).Take(2).Reverse().ToList();

ปัญหาที่ฉันมีกับสิ่งนี้คือถ้าฉันพูดว่า: List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse(); ฉันได้รับข้อผิดพลาดคอมไพเลอร์เพราะ. Reverse () ผลตอบแทนที่ได้เป็นโมฆะและคอมไพเลอร์เลือกวิธีการนั้นแทน Linq หนึ่งที่ส่งกลับ IEnumerable ข้อเสนอแนะ?
Clinton Pierce

1
คุณสามารถแก้ปัญหานี้ได้โดยการร่าย mystring ไปที่ IEnumerable <String>: ((IEnumerable <String>) mystring). Reverse () ใช้ (2). Reverse ()
Jan Hettich

ง่ายและเรียบง่ายพอ แต่ต้องการกลับคำสั่งซื้อสองครั้งอย่างสมบูรณ์ นี่อาจเป็นวิธีที่ดีที่สุด
shashwat

ฉันชอบมันนอกเหนือจากคำตอบที่ยอมรับจาก kbrimington หากคุณไม่สนใจเกี่ยวกับการสั่งซื้อหลังจากที่คุณมีNบันทึกล่าสุดคุณสามารถข้ามรายการที่สองReverseได้
ZoolWay

@shashwat มันไม่ได้กลับคำสั่งสองครั้ง "สมบูรณ์" การกลับรายการที่สองใช้กับการรวบรวมรายการ N เท่านั้น นอกจากนี้ยังขึ้นอยู่กับว่า Reverse () ถูกนำไปใช้งานการเรียกครั้งแรกไปยังมันอาจย้อนกลับรายการ N เท่านั้น (การดำเนินการ. NET 4.0 จะคัดลอกคอลเลกชันไปยังอาร์เรย์และทำดัชนีย้อนหลังออกไป)
James Curran

47

หมายเหตุ : ฉันพลาดชื่อคำถามของคุณซึ่งกล่าวว่าใช้ Linqดังนั้นคำตอบของฉันไม่ได้ใช้ Linq

หากคุณต้องการหลีกเลี่ยงการแคชสำเนาทั้งคอลเลคชั่นที่ไม่ขี้เกียจคุณสามารถเขียนวิธีการง่ายๆที่ใช้รายการที่เชื่อมโยง

วิธีการต่อไปนี้จะเพิ่มแต่ละค่าที่พบในคอลเลกชันดั้งเดิมลงในรายการที่เชื่อมโยงและตัดรายการที่เชื่อมโยงลงไปตามจำนวนรายการที่ต้องการ เนื่องจากมันทำให้รายการที่เชื่อมโยงถูกตัดออกเป็นจำนวนไอเท็มนี้ตลอดเวลาผ่านการวนซ้ำผ่านคอลเลกชันมันจะเก็บสำเนาของไอเท็ม N ส่วนใหญ่จากคอลเล็กชันดั้งเดิมเท่านั้น

ไม่ต้องการให้คุณทราบจำนวนรายการในคอลเลกชันดั้งเดิมหรือวนซ้ำมากกว่าหนึ่งครั้ง

การใช้งาน:

IEnumerable<int> sequence = Enumerable.Range(1, 10000);
IEnumerable<int> last10 = sequence.TakeLast(10);
...

วิธีการขยาย:

public static class Extensions
{
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> collection,
        int n)
    {
        if (collection == null)
            throw new ArgumentNullException(nameof(collection));
        if (n < 0)
            throw new ArgumentOutOfRangeException(nameof(n), $"{nameof(n)} must be 0 or greater");

        LinkedList<T> temp = new LinkedList<T>();

        foreach (var value in collection)
        {
            temp.AddLast(value);
            if (temp.Count > n)
                temp.RemoveFirst();
        }

        return temp;
    }
}

ฉันยังคิดว่าคุณมีคำตอบที่ดีและถูกต้องแม้ว่าจะไม่ได้ใช้ Linq ในทางเทคนิคดังนั้นฉันจึงยังให้ +1 :)
Matthew Groves

ทำความสะอาดเรียบร้อยและขยายได้ +1!
Yasser Shaikh

1
ฉันคิดว่ามันเป็นทางออกเดียวที่ไม่ทำให้ตัวแจงนับต้นทางทำงานผ่านสองครั้ง (หรือมากกว่า) และไม่บังคับให้ประมวลผลการแจงนับเป็นรูปธรรมดังนั้นในแอปพลิเคชันส่วนใหญ่ฉันจะบอกว่ามันจะมีประสิทธิภาพมากกว่า ของหน่วยความจำและความเร็ว
Sprotty

30

นี่คือวิธีการที่ใช้งานได้กับการนับจำนวนใด ๆ แต่ใช้หน่วยเก็บชั่วคราว O (N) เท่านั้น:

public static class TakeLastExtension
{
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int takeCount)
    {
        if (source == null) { throw new ArgumentNullException("source"); }
        if (takeCount < 0) { throw new ArgumentOutOfRangeException("takeCount", "must not be negative"); }
        if (takeCount == 0) { yield break; }

        T[] result = new T[takeCount];
        int i = 0;

        int sourceCount = 0;
        foreach (T element in source)
        {
            result[i] = element;
            i = (i + 1) % takeCount;
            sourceCount++;
        }

        if (sourceCount < takeCount)
        {
            takeCount = sourceCount;
            i = 0;
        }

        for (int j = 0; j < takeCount; ++j)
        {
            yield return result[(i + j) % takeCount];
        }
    }
}

การใช้งาน:

List<int> l = new List<int> {4, 6, 3, 6, 2, 5, 7};
List<int> lastElements = l.TakeLast(3).ToList();

มันทำงานโดยใช้วงแหวนบัฟเฟอร์ขนาด N เพื่อจัดเก็บองค์ประกอบตามที่เห็นพวกเขาเขียนทับองค์ประกอบเก่ากับองค์ประกอบใหม่ เมื่อถึงจุดสิ้นสุดของการนับได้ถึงบัฟเฟอร์แหวนประกอบด้วยองค์ประกอบ N ล่าสุด


2
1: นี้ควรจะมีประสิทธิภาพการทำงานที่ดีกว่าฉัน nแต่คุณควรให้แน่ใจว่ามันจะเป็นสิ่งที่ถูกต้องเมื่อคอลเลกชันที่มีองค์ประกอบน้อยกว่า
Lasse V. Karlsen

ส่วนใหญ่ฉันคิดว่าผู้คนจะดูแลเมื่อคัดลอกรหัสจาก SO เพื่อใช้ในการผลิตเพื่อเพิ่มสิ่งต่าง ๆ ด้วยตนเองมันอาจจะไม่เป็นปัญหา หากคุณกำลังจะเพิ่มให้ลองตรวจสอบตัวแปรคอลเลกชันสำหรับค่าว่างเช่นกัน ไม่อย่างนั้นวิธีแก้ปัญหาที่ยอดเยี่ยม :) ฉันกำลังพิจารณาใช้ตัวบัฟเฟอร์วงแหวนเพราะรายการที่เชื่อมโยงจะเพิ่มแรงกดดัน GC แต่ก็ไม่นานเพราะฉันทำแล้วและฉันไม่ต้องการยุ่งกับการทดสอบรหัสเพื่อหา ถ้าฉันทำมันถูกต้อง ฉันต้องบอกว่าฉันตกหลุมรักกับ LINQPad แล้ว :) linqpad.net
Lasse V. Karlsen

2
การปรับให้เหมาะสมที่เป็นไปได้คือการตรวจสอบว่า IList ที่นำมาใช้นับไม่ได้และใช้วิธีแก้ปัญหาเล็ก ๆ น้อย ๆ หรือไม่ถ้าใช่ วิธีการจัดเก็บข้อมูลชั่วคราวนั้นจะต้องใช้สำหรับ IEnumerables 'สตรีมมิ่ง' อย่างแท้จริงเท่านั้น
piers7

1
จิ๊บจ๊อยจู้จี้รับ: ข้อโต้แย้งของคุณเพื่อ ArgumentOutOfRangeException อยู่ในคำสั่งที่ไม่ถูกต้อง (R # พูด)
piers7

28

.NET Core 2.0+ ให้วิธีการ LINQ TakeLast():

https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast

ตัวอย่าง :

Enumerable
    .Range(1, 10)
    .TakeLast(3) // <--- takes last 3 items
    .ToList()
    .ForEach(i => System.Console.WriteLine(i))

// outputs:
// 8
// 9
// 10

ฉันกำลังใช้: NET Standard 2.0 และฉันไม่มีให้ เกิดอะไรขึ้น :(
SuperJMN

@SuperJMN แม้ว่าคุณอาจอ้างอิงไลบรารี. net มาตรฐาน 2.0 คุณอาจไม่ได้กำหนดรุ่น dotnet core ที่ถูกต้องในโครงการของคุณ วิธีนี้ไม่สามารถใช้ได้สำหรับ v1.x ( netcoreapp1.x) แต่เฉพาะสำหรับ v2.0 & v2.1 ของ dotnetcore ( netcoreapp2.x) เป็นไปได้ว่าคุณอาจกำหนดเป้าหมายเป็นเฟรมเวิร์กแบบเต็ม (เช่นnet472) ซึ่งไม่รองรับเช่นกัน (. Lib มาตรฐานมาตรฐานสามารถใช้งานได้จากข้อใดข้อหนึ่งข้างต้น แต่อาจเปิดเผย API บางตัวที่เฉพาะเจาะจงกับเฟรมเวิร์กเป้าหมายดูdocs.microsoft.com/en-us/dotnet/standard/frameworks )
เรย์

1
สิ่งเหล่านี้จำเป็นต้องสูงขึ้นในขณะนี้ ไม่จำเป็นต้องคิดค้นล้อใหม่อีกครั้ง
James Woodley

11

ฉันกำลังประหลาดใจที่ไม่มีใครได้พูดถึงเรื่องนี้ แต่ SkipWhile จะมีวิธีการที่ใช้ดัชนีองค์ประกอบของ

public static IEnumerable<T> TakeLastN<T>(this IEnumerable<T> source, int n)
{
    if (source == null)
        throw new ArgumentNullException("Source cannot be null");

    int goldenIndex = source.Count() - n;
    return source.SkipWhile((val, index) => index < goldenIndex);
}

//Or if you like them one-liners (in the spirit of the current accepted answer);
//However, this is most likely impractical due to the repeated calculations
collection.SkipWhile((val, index) => index < collection.Count() - N)

ประโยชน์ที่รับรู้ได้เพียงอย่างเดียวที่โซลูชันนี้นำเสนอเหนือผู้อื่นคือคุณสามารถมีตัวเลือกในการเพิ่มคำกริยาเพื่อสร้างเคียวรี LINQ ที่ทรงพลังและมีประสิทธิภาพมากกว่าแทนที่จะแยกการดำเนินการสองอย่างที่สำรวจ IEnumerable สองครั้ง

public static IEnumerable<T> FilterLastN<T>(this IEnumerable<T> source, int n, Predicate<T> pred)
{
    int goldenIndex = source.Count() - n;
    return source.SkipWhile((val, index) => index < goldenIndex && pred(val));
}

9

ใช้ EnumerableEx.TakeLast ในแอสเซมบลี System.Interactive ของ RX เป็นการใช้งาน O (N) เช่น @ Mark's แต่ใช้คิวแทนการสร้าง ring-buffer (และ dequeues รายการเมื่อถึงความจุบัฟเฟอร์)

(หมายเหตุ: นี่คือรุ่น IEnumerable - ไม่ใช่รุ่น IObservable แม้ว่าการใช้งานของทั้งสองจะเหมือนกันมาก)


นี่คือคำตอบที่ดีที่สุด อย่าม้วนตัวคุณเองถ้ามีห้องสมุดที่เหมาะสมที่ทำงานได้และทีม RX มีคุณภาพสูง
bradgonesurfing

หากคุณกำลังจะไปกับสิ่งนี้ติดตั้งจาก Nuget - nuget.org/packages/Ix-Async
nikib3ro

C # Queue<T>ใช้งานไม่ได้โดยใช้บัฟเฟอร์แบบวงกลมใช่ไหม
tigrou

@tigrou ไม่มันไม่ได้เป็นแบบวงกลม
citykid


6

หากคุณกำลังจัดการกับคอลเลกชันที่มีคีย์ (เช่นรายการจากฐานข้อมูล) โซลูชันที่รวดเร็ว (เช่นเร็วกว่าคำตอบที่เลือก) จะเป็น

collection.OrderByDescending(c => c.Key).Take(3).OrderBy(c => c.Key);

+1 ใช้งานได้สำหรับฉันและอ่านง่ายฉันมีวัตถุจำนวนเล็กน้อยในรายการของฉัน
fubo

5

หากคุณไม่สนใจที่จะจิ้ม Rx เป็นส่วนหนึ่งของ monad คุณสามารถใช้TakeLast:

IEnumerable<int> source = Enumerable.Range(1, 10000);

IEnumerable<int> lastThree = source.AsObservable().TakeLast(3).AsEnumerable();

2
คุณไม่ต้องการ AsObservable () ถ้าคุณอ้างอิง RX ของ System.Interactive แทนที่จะเป็น
System


2

ฉันพยายามรวมประสิทธิภาพและความเรียบง่ายเข้าด้วยกัน:

public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
{
    if (source == null) { throw new ArgumentNullException("source"); }

    Queue<T> lastElements = new Queue<T>();
    foreach (T element in source)
    {
        lastElements.Enqueue(element);
        if (lastElements.Count > count)
        {
            lastElements.Dequeue();
        }
    }

    return lastElements;
}

เกี่ยวกับประสิทธิภาพ: ใน C # Queue<T>มีการใช้งานโดยใช้บัฟเฟอร์แบบวงกลมดังนั้นจึงไม่มีการสร้างอินสแตนซ์ของวัตถุในแต่ละลูป (เฉพาะเมื่อคิวโตขึ้น) ผมไม่ได้ตั้งความจุคิว (ใช้เฉพาะคอนสตรัค) count = int.MaxValueเพราะใครบางคนอาจจะเรียกส่วนขยายนี้ด้วย เพื่อประสิทธิภาพที่เพิ่มขึ้นคุณอาจตรวจสอบว่ามีการนำซอร์สมาใช้หรือไม่IList<T>และหากใช่ให้ดึงค่าสุดท้ายโดยตรงโดยใช้ดัชนีอาร์เรย์


1

มันไม่มีประสิทธิภาพเล็กน้อยที่จะใช้ N สุดท้ายของคอลเล็กชันโดยใช้ LINQ เนื่องจากโซลูชันทั้งหมดข้างต้นต้องการการวนซ้ำในคอลเลกชัน TakeLast(int n)ในSystem.Interactiveยังมีปัญหานี้

หากคุณมีรายการสิ่งที่มีประสิทธิภาพมากขึ้นที่จะทำคือชิ้นโดยใช้วิธีการดังต่อไปนี้

/// Select from start to end exclusive of end using the same semantics
/// as python slice.
/// <param name="list"> the list to slice</param>
/// <param name="start">The starting index</param>
/// <param name="end">The ending index. The result does not include this index</param>
public static List<T> Slice<T>
(this IReadOnlyList<T> list, int start, int? end = null)
{
    if (end == null)
    {
        end = list.Count();
    }
     if (start < 0)
    {
        start = list.Count + start;
    }
     if (start >= 0 && end.Value > 0 && end.Value > start)
    {
        return list.GetRange(start, end.Value - start);
    }
     if (end < 0)
    {
        return list.GetRange(start, (list.Count() + end.Value) - start);
    }
     if (end == start)
    {
        return new List<T>();
    }
     throw new IndexOutOfRangeException(
        "count = " + list.Count() + 
        " start = " + start +
        " end = " + end);
}

กับ

public static List<T> GetRange<T>( this IReadOnlyList<T> list, int index, int count )
{
    List<T> r = new List<T>(count);
    for ( int i = 0; i < count; i++ )
    {
        int j=i + index;
        if ( j >= list.Count )
        {
            break;
        }
        r.Add(list[j]);
    }
    return r;
}

และบางกรณีทดสอบ

[Fact]
public void GetRange()
{
    IReadOnlyList<int> l = new List<int>() { 0, 10, 20, 30, 40, 50, 60 };
     l
        .GetRange(2, 3)
        .ShouldAllBeEquivalentTo(new[] { 20, 30, 40 });
     l
        .GetRange(5, 10)
        .ShouldAllBeEquivalentTo(new[] { 50, 60 });

}
 [Fact]
void SliceMethodShouldWork()
{
    var list = new List<int>() { 1, 3, 5, 7, 9, 11 };
    list.Slice(1, 4).ShouldBeEquivalentTo(new[] { 3, 5, 7 });
    list.Slice(1, -2).ShouldBeEquivalentTo(new[] { 3, 5, 7 });
    list.Slice(1, null).ShouldBeEquivalentTo(new[] { 3, 5, 7, 9, 11 });
    list.Slice(-2)
        .Should()
        .BeEquivalentTo(new[] {9, 11});
     list.Slice(-2,-1 )
        .Should()
        .BeEquivalentTo(new[] {9});
}

1

ฉันรู้ว่ามันสายเกินไปที่จะตอบคำถามนี้ แต่ถ้าคุณกำลังทำงานกับคอลเลกชันประเภท IList <> และคุณไม่สนใจเกี่ยวกับคำสั่งของคอลเลกชันที่ส่งคืนแล้ววิธีนี้จะทำงานได้เร็วขึ้น ฉันใช้คำตอบของ Mark Byersแล้วทำการเปลี่ยนแปลงเล็กน้อย ดังนั้นตอนนี้วิธี TakeLast คือ:

public static IEnumerable<T> TakeLast<T>(IList<T> source, int takeCount)
{
    if (source == null) { throw new ArgumentNullException("source"); }
    if (takeCount < 0) { throw new ArgumentOutOfRangeException("takeCount", "must not be negative"); }
    if (takeCount == 0) { yield break; }

    if (source.Count > takeCount)
    {
        for (int z = source.Count - 1; takeCount > 0; z--)
        {
            takeCount--;
            yield return source[z];
        }
    }
    else
    {
        for(int i = 0; i < source.Count; i++)
        {
            yield return source[i];
        }
    }
}

สำหรับการทดสอบฉันได้ใช้วิธีการมาร์ค Byers และ andswer นี่คือการทดสอบ:

IList<int> test = new List<int>();
for(int i = 0; i<1000000; i++)
{
    test.Add(i);
}

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

IList<int> result = TakeLast(test, 10).ToList();

stopwatch.Stop();

Stopwatch stopwatch1 = new Stopwatch();
stopwatch1.Start();

IList<int> result1 = TakeLast2(test, 10).ToList();

stopwatch1.Stop();

Stopwatch stopwatch2 = new Stopwatch();
stopwatch2.Start();

IList<int> result2 = test.Skip(Math.Max(0, test.Count - 10)).Take(10).ToList();

stopwatch2.Stop();

และนี่คือผลลัพธ์สำหรับการรับ 10 องค์ประกอบ:

ป้อนคำอธิบายรูปภาพที่นี่

และสำหรับการรับผล 1000001 องค์ประกอบคือ: ป้อนคำอธิบายรูปภาพที่นี่


1

นี่คือทางออกของฉัน:

public static class EnumerationExtensions
{
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> input, int count)
    {
        if (count <= 0)
            yield break;

        var inputList = input as IList<T>;

        if (inputList != null)
        {
            int last = inputList.Count;
            int first = last - count;

            if (first < 0)
                first = 0;

            for (int i = first; i < last; i++)
                yield return inputList[i];
        }
        else
        {
            // Use a ring buffer. We have to enumerate the input, and we don't know in advance how many elements it will contain.
            T[] buffer = new T[count];

            int index = 0;

            count = 0;

            foreach (T item in input)
            {
                buffer[index] = item;

                index = (index + 1) % buffer.Length;
                count++;
            }

            // The index variable now points at the next buffer entry that would be filled. If the buffer isn't completely
            // full, then there are 'count' elements preceding index. If the buffer *is* full, then index is pointing at
            // the oldest entry, which is the first one to return.
            //
            // If the buffer isn't full, which means that the enumeration has fewer than 'count' elements, we'll fix up
            // 'index' to point at the first entry to return. That's easy to do; if the buffer isn't full, then the oldest
            // entry is the first one. :-)
            //
            // We'll also set 'count' to the number of elements to be returned. It only needs adjustment if we've wrapped
            // past the end of the buffer and have enumerated more than the original count value.

            if (count < buffer.Length)
                index = 0;
            else
                count = buffer.Length;

            // Return the values in the correct order.
            while (count > 0)
            {
                yield return buffer[index];

                index = (index + 1) % buffer.Length;
                count--;
            }
        }
    }

    public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> input, int count)
    {
        if (count <= 0)
            return input;
        else
            return input.SkipLastIter(count);
    }

    private static IEnumerable<T> SkipLastIter<T>(this IEnumerable<T> input, int count)
    {
        var inputList = input as IList<T>;

        if (inputList != null)
        {
            int first = 0;
            int last = inputList.Count - count;

            if (last < 0)
                last = 0;

            for (int i = first; i < last; i++)
                yield return inputList[i];
        }
        else
        {
            // Aim to leave 'count' items in the queue. If the input has fewer than 'count'
            // items, then the queue won't ever fill and we return nothing.

            Queue<T> elements = new Queue<T>();

            foreach (T item in input)
            {
                elements.Enqueue(item);

                if (elements.Count > count)
                    yield return elements.Dequeue();
            }
        }
    }
}

รหัสเป็นบิตหนา แต่เป็นองค์ประกอบที่นำกลับมาใช้ใหม่ได้มันควรทำงานได้ดีเท่าที่จะทำได้ในสถานการณ์ส่วนใหญ่และมันจะทำให้รหัสที่ใช้นั้นดีและกระชับ :-)

ของฉันTakeLastสำหรับที่ไม่ใช่ -IList`1ขึ้นอยู่กับอัลกอริธึมบัฟเฟอร์บัฟเฟอร์แบบเดียวกันกับที่อยู่ในคำตอบของ @Mark Byers และ @MackieChan มันน่าสนใจว่ามันคล้ายกันแค่ไหน - ฉันเขียนของฉันเองอย่างสมบูรณ์ เดาว่ามีวิธีเดียวที่จะทำบัฟเฟอร์ของแหวนอย่างถูกต้อง :-)

เมื่อดูที่คำตอบของ @ kbrimington การตรวจสอบเพิ่มเติมสามารถเพิ่มลงในนี้IQuerable<T>เพื่อย้อนกลับไปยังวิธีการที่ทำงานได้ดีกับ Entity Framework - โดยสมมติว่าสิ่งที่ฉันมี ณ จุดนี้ไม่ได้


0

ด้านล่างตัวอย่างจริงวิธีรับองค์ประกอบ 3 รายการล่าสุดจากคอลเลกชัน (อาร์เรย์):

// split address by spaces into array
string[] adrParts = adr.Split(new string[] { " " },StringSplitOptions.RemoveEmptyEntries);
// take only 3 last items in array
adrParts = adrParts.SkipWhile((value, index) => { return adrParts.Length - index > 3; }).ToArray();

0

ใช้วิธีนี้เพื่อรับช่วงทั้งหมดโดยไม่มีข้อผิดพลาด

 public List<T> GetTsRate( List<T> AllT,int Index,int Count)
        {
            List<T> Ts = null;
            try
            {
                Ts = AllT.ToList().GetRange(Index, Count);
            }
            catch (Exception ex)
            {
                Ts = AllT.Skip(Index).ToList();
            }
            return Ts ;
        }

0

การใช้งานที่แตกต่างกันเล็กน้อยกับการใช้งานของบัฟเฟอร์แบบวงกลม การวัดประสิทธิภาพแสดงให้เห็นว่าวิธีนี้เร็วกว่าการใช้Queueสองเท่า(การใช้งานของTakeLastในSystem.Linq ) แต่ไม่ต้องเสียค่าใช้จ่าย - มันต้องการบัฟเฟอร์ที่เพิ่มขึ้นตามจำนวนองค์ประกอบที่ร้องขอแม้ว่าคุณจะมี ชุดเล็กคุณสามารถได้รับการจัดสรรหน่วยความจำขนาดใหญ่

public IEnumerable<T> TakeLast<T>(IEnumerable<T> source, int count)
{
    int i = 0;

    if (count < 1)
        yield break;

    if (source is IList<T> listSource)
    {
        if (listSource.Count < 1)
            yield break;

        for (i = listSource.Count < count ? 0 : listSource.Count - count; i < listSource.Count; i++)
            yield return listSource[i];

    }
    else
    {
        bool move = true;
        bool filled = false;
        T[] result = new T[count];

        using (var enumerator = source.GetEnumerator())
            while (move)
            {
                for (i = 0; (move = enumerator.MoveNext()) && i < count; i++)
                    result[i] = enumerator.Current;

                filled |= move;
            }

        if (filled)
            for (int j = i; j < count; j++)
                yield return result[j];

        for (int j = 0; j < i; j++)
            yield return result[j];

    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.