Sử dụng Linq để có được các yếu tố N cuối cùng của bộ sưu tập?


284

Đưa ra một bộ sưu tập, có cách nào để có được các yếu tố N cuối cùng của bộ sưu tập đó không? Nếu không có một phương thức nào trong khung, thì cách tốt nhất để viết một phương thức mở rộng là gì?

Câu trả lời:


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

Cách tiếp cận này bảo tồn thứ tự vật phẩm mà không phụ thuộc vào bất kỳ sự sắp xếp nào và có khả năng tương thích rộng trên một số nhà cung cấp LINQ.

Điều quan trọng là phải cẩn thận không gọi Skipvới số âm. Một số nhà cung cấp, chẳng hạn như Entity Framework, sẽ tạo ra một ArgumentException khi được trình bày với một đối số phủ định. Các cuộc gọi để Math.Maxtránh điều này gọn gàng.

Lớp bên dưới có tất cả các yếu tố cần thiết cho các phương thức mở rộng, đó là: một lớp tĩnh, một phương thức tĩnh và sử dụng thistừ khóa.

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));
    }
}

Một lưu ý ngắn gọn về hiệu suất:

Vì lệnh gọi Count()có thể gây ra sự liệt kê của một số cấu trúc dữ liệu nhất định, phương pháp này có nguy cơ gây ra hai lần truyền dữ liệu. Đây thực sự không phải là một vấn đề với hầu hết các liệt kê; trong thực tế, tối ưu hóa đã tồn tại cho các truy vấn Danh sách, Mảng và thậm chí cả EF để đánh giá Count()hoạt động trong thời gian O (1).

Tuy nhiên, nếu bạn phải sử dụng một số đếm chỉ chuyển tiếp và muốn tránh thực hiện hai đường chuyền, hãy xem xét thuật toán một lượt như Lasse V. Karlsen hoặc Mark Byers mô tả. Cả hai cách tiếp cận này đều sử dụng bộ đệm tạm thời để giữ các vật phẩm trong khi liệt kê, được mang lại khi kết thúc bộ sưu tập được tìm thấy.


2
+1, vì điều này hoạt động trong Linq to Entities / SQL. Tôi đoán nó cũng hiệu quả hơn trong Linq to Object so với chiến lược của James Curran.
StriplingWar Warrior

11
Phụ thuộc vào bản chất của bộ sưu tập. Đếm () có thể là O (N).
James Curran

3
@James: Hoàn toàn chính xác. Nếu xử lý nghiêm các bộ sưu tập IEnumerable, đây có thể là truy vấn hai lượt. Tôi rất muốn thấy thuật toán 1-pass được đảm bảo. Nó có thể hữu ích.
kbrimington

4
Đã làm một số điểm chuẩn. Hóa ra LINQ to Object thực hiện một số tối ưu hóa dựa trên loại bộ sưu tập bạn đang sử dụng. Sử dụng mảng, Lists và LinkedLists, giải pháp của James có xu hướng nhanh hơn, mặc dù không phải theo một độ lớn. Nếu IEnumerable được tính toán (thông qua Enumerable.Range, ví dụ), giải pháp của James sẽ mất nhiều thời gian hơn. Tôi không thể nghĩ ra bất kỳ cách nào để đảm bảo một lượt đi mà không biết gì về việc triển khai hoặc sao chép các giá trị vào một cấu trúc dữ liệu khác.
StriplingWar Warrior

1
@RedFilter - Đủ công bằng. Tôi cho rằng thói quen phân trang của tôi bị rò rỉ ở đây. Cảm ơn cho đôi mắt sắc sảo của bạn.
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();
}

CẬP NHẬT: Để giải quyết vấn đề của clintp: a) Sử dụng phương thức TakeLast () mà tôi đã xác định ở trên sẽ giải quyết vấn đề, nhưng nếu bạn thực sự muốn làm điều đó mà không có phương thức bổ sung, thì bạn chỉ cần nhận ra rằng trong khi Enumerable.Reverse () có thể được sử dụng như một phương thức mở rộng, bạn không bắt buộc phải sử dụng theo cách đó:

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

Vấn đề tôi gặp phải với vấn đề này là nếu tôi nói: List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse(); Tôi gặp lỗi trình biên dịch vì .Reverse () trả về void và trình biên dịch chọn phương thức đó thay vì Linq trả về IEnumerable. Gợi ý?
Clinton Pierce

1
Bạn có thể giải quyết vấn đề này bằng cách truyền một cách rõ ràng việc tạo bí ẩn cho IEnumerable <String>: ((IEnumerable <String>) mystring) .Reverse (). Take (2) .Reverse ()
Jan Hettich

Dễ dàng và đủ đơn giản nhưng yêu cầu đảo ngược thứ tự hai lần hoàn toàn. Đây có thể là cách tốt nhất
shashwat

Tôi thích nó ngoài câu trả lời được chấp nhận từ kbrimington. Nếu bạn không quan tâm đến đơn hàng sau khi bạn có Nhồ sơ cuối cùng, bạn có thể bỏ qua lần thứ hai Reverse.
ZoolWay

@shashwat Nó không đảo ngược thứ tự hai lần "hoàn toàn". Sự đảo ngược thứ hai chỉ áp dụng cho bộ sưu tập N mặt hàng. Hơn nữa, tùy thuộc vào cách Reverse () được triển khai, cuộc gọi đầu tiên đến nó chỉ có thể đảo ngược N mục. (Việc triển khai .NET 4.0 sẽ sao chép bộ sưu tập vào một mảng và lập chỉ mục ngược lại nó)
James Curran

47

Lưu ý : Tôi đã bỏ lỡ tiêu đề câu hỏi của bạn có sử dụng Linq , vì vậy trên thực tế câu trả lời của tôi không sử dụng Linq.

Nếu bạn muốn tránh lưu vào bộ đệm một bản sao không lười biếng của toàn bộ bộ sưu tập, bạn có thể viết một phương thức đơn giản sử dụng danh sách được liên kết.

Phương pháp sau đây sẽ thêm từng giá trị mà nó tìm thấy trong bộ sưu tập gốc vào danh sách được liên kết và cắt danh sách được liên kết xuống theo số lượng mục cần thiết. Vì nó giữ cho danh sách được liên kết được cắt tỉa theo số lượng mục này trong toàn bộ thời gian thông qua việc lặp qua bộ sưu tập, nên nó sẽ chỉ giữ một bản sao của hầu hết N mục từ bộ sưu tập ban đầu.

Nó không yêu cầu bạn phải biết số lượng vật phẩm trong bộ sưu tập ban đầu, cũng không lặp đi lặp lại nhiều lần.

Sử dụng:

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

Phương pháp mở rộng:

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;
    }
}

Tôi vẫn nghĩ rằng bạn có câu trả lời tốt, hợp lệ ngay cả khi không sử dụng Linq về mặt kỹ thuật, vì vậy tôi vẫn cho bạn +1 :)
Matthew Groves

sạch sẽ, gọn gàng và có thể mở rộng +1!
Yasser Shaikh

1
Tôi nghĩ rằng giải pháp duy nhất của nó không làm cho trình liệt kê nguồn được chạy qua hai lần (hoặc nhiều hơn) và không buộc thực hiện phép liệt kê, vì vậy trong hầu hết các ứng dụng tôi sẽ nói rằng nó sẽ hiệu quả hơn nhiều về mặt thuật ngữ của bộ nhớ và tốc độ.
Sprotty

30

Đây là một phương thức hoạt động trên mọi liệt kê nhưng chỉ sử dụng bộ lưu trữ tạm thời 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];
        }
    }
}

Sử dụng:

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

Nó hoạt động bằng cách sử dụng bộ đệm vòng có kích thước N để lưu trữ các phần tử khi nhìn thấy chúng, ghi đè các phần tử cũ bằng phần tử mới. Khi kết thúc vô số, bộ đệm vòng chứa N phần tử cuối cùng.


2
+1: Điều này sẽ có hiệu suất tốt hơn của tôi, nhưng bạn nên đảm bảo rằng nó thực hiện đúng khi bộ sưu tập chứa ít yếu tố hơn n.
Lasse V. Karlsen

Chà, hầu hết thời gian tôi cho rằng mọi người sẽ cẩn thận khi sao chép mã từ SO để sử dụng sản xuất để tự thêm những thứ như vậy, nó có thể không phải là vấn đề. Nếu bạn sẽ thêm nó, hãy xem xét việc kiểm tra biến bộ sưu tập cho null. Mặt khác, giải pháp tuyệt vời :) Tôi đã cân nhắc sử dụng bộ đệm vòng, bởi vì một danh sách được liên kết sẽ thêm áp lực GC, nhưng đã được một thời gian kể từ khi tôi làm một cái và tôi không muốn gặp rắc rối với mã kiểm tra để tìm ra nếu tôi làm đúng Tôi phải nói rằng tôi đang yêu LINQPad :) linqpad.net
Lasse V. Karlsen

2
Một tối ưu hóa có thể sẽ là kiểm tra xem IList có thực hiện được không, và sử dụng giải pháp tầm thường nếu có. Cách tiếp cận lưu trữ tạm thời sau đó sẽ chỉ cần thiết cho 'phát trực tuyến' IEnumerables
piers7

1
nit-pick tầm thường: các đối số của bạn đối với ArgumentOutOfRangeException không đúng thứ tự (R # nói)
piers7

28

.NET Core 2.0+ cung cấp phương thức LINQ TakeLast():

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

ví dụ :

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

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

Tôi đang sử dụng: NET Standard 2.0 và tôi không có sẵn. Chuyện gì vậy? :(
SuperJMN

@SuperJMN Mặc dù bạn có thể tham khảo các thư viện .net tiêu chuẩn 2.0, nhưng bạn có thể không nhắm mục tiêu đúng phiên bản lõi dotnet trong dự án của mình. Phương thức này không có sẵn cho v1.x ( netcoreapp1.x) mà chỉ dành cho v2.0 & v2.1 của dotnetcore ( netcoreapp2.x). Có thể bạn có thể nhắm mục tiêu khung đầy đủ (ví dụ net472) cũng không được hỗ trợ. (libs tiêu chuẩn .net có thể được sử dụng bởi bất kỳ điều nào ở trên nhưng chỉ có thể hiển thị một số API nhất định cụ thể cho khung mục tiêu. xem docs.microsoft.com/en-us/dotnet/stiteria/frameworks )
Ray

1
Những nhu cầu này phải được cao hơn bây giờ. Không cần phải phát minh lại bánh xe
James Woodley

11

Tôi ngạc nhiên khi không ai nhắc đến nó, nhưng SkipWhile có một phương pháp sử dụng chỉ mục của phần tử .

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)

Lợi ích duy nhất có thể nhận thấy mà giải pháp này mang lại cho người khác là bạn có thể có tùy chọn để thêm vào một vị từ để tạo một truy vấn LINQ mạnh mẽ và hiệu quả hơn, thay vì có hai thao tác riêng biệt đi qua IEnumerable hai lần.

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

Sử dụng EnumerableEx.TakeLast trong lắp ráp System.Interactive của RX. Đó là một triển khai O (N) như @ Mark, nhưng nó sử dụng hàng đợi thay vì cấu trúc bộ đệm vòng (và loại bỏ các mục khi đạt đến dung lượng bộ đệm).

(NB: Đây là phiên bản IEnumerable - không phải phiên bản IObservable, mặc dù việc triển khai hai phiên bản này khá giống nhau)


Đây là câu trả lời tốt nhất. Đừng tự lăn lộn nếu có một thư viện phù hợp thực hiện công việc và nhóm RX có chất lượng cao.
bradgonesurfing

Nếu bạn đang làm điều này, hãy cài đặt nó từ Nuget - nuget.org/packages/Ix-Async
nikib3ro

Không C # được Queue<T>thực hiện bằng cách sử dụng bộ đệm tròn ?
tigrou

@tigrou. không, nó không phải là thông tư
citykid


6

Nếu bạn đang xử lý một bộ sưu tập bằng một khóa (ví dụ: các mục từ cơ sở dữ liệu), một giải pháp nhanh (tức là nhanh hơn câu trả lời đã chọn) sẽ là

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

+1 hoạt động với tôi và nó rất dễ đọc, tôi có một số lượng nhỏ các đối tượng trong danh sách của mình
fubo

5

Nếu bạn không ngại nhúng vào Rx như một phần của đơn nguyên, bạn có thể sử dụng TakeLast:

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

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

2
Bạn không cần AsObservable () nếu bạn tham khảo System của RX.Interactive thay vì System.Reactive (xem câu trả lời của tôi)
piers7

2

Nếu sử dụng thư viện của bên thứ ba là một tùy chọn, MoreLinq sẽ xác định TakeLast()chính xác việc này.


2

Tôi đã cố gắng kết hợp hiệu quả và đơn giản và kết thúc với điều này:

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;
}

Về hiệu suất: Trong C #, Queue<T>được triển khai bằng cách sử dụng bộ đệm tròn để không có việc khởi tạo đối tượng được thực hiện mỗi vòng lặp (chỉ khi hàng đợi tăng lên). Tôi không đặt dung lượng hàng đợi (sử dụng hàm tạo chuyên dụng) vì ai đó có thể gọi phần mở rộng này với count = int.MaxValue. Để có hiệu suất cao hơn, bạn có thể kiểm tra xem nguồn có thực hiện IList<T>hay không và nếu có, hãy trích xuất trực tiếp các giá trị cuối cùng bằng cách sử dụng các chỉ mục mảng.


1

Việc lấy N cuối cùng của bộ sưu tập bằng LINQ là không hiệu quả vì tất cả các giải pháp trên đều yêu cầu lặp lại trên toàn bộ sưu tập. TakeLast(int n)trong System.Interactivecũng có vấn đề này.

Nếu bạn có một danh sách, một việc hiệu quả hơn là cắt nó bằng phương pháp sau

/// 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);
}

với

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;
}

và một số trường hợp thử nghiệm

[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

Tôi biết đã muộn để trả lời câu hỏi này. Nhưng nếu bạn đang làm việc với bộ sưu tập loại IList <> và bạn không quan tâm đến thứ tự của bộ sưu tập được trả về, thì phương pháp này hoạt động nhanh hơn. Tôi đã sử dụng câu trả lời của Mark Byers và thực hiện một số thay đổi nhỏ. Vì vậy, bây giờ phương thức TakeLast là:

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];
        }
    }
}

Để thử nghiệm, tôi đã sử dụng phương pháp Mark Byers và kbrimington's andswer . Đây là bài kiểm tra:

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();

Và đây là kết quả để lấy 10 yếu tố:

nhập mô tả hình ảnh ở đây

và để lấy 1000001 yếu tố kết quả là: nhập mô tả hình ảnh ở đây


1

Đây là giải pháp của tôi:

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();
            }
        }
    }
}

Mã này hơi chunky, nhưng là một thành phần có thể tái sử dụng, nó sẽ hoạt động tốt như trong hầu hết các kịch bản, và nó sẽ giữ cho mã sử dụng nó tốt và ngắn gọn. :-)

Của tôi TakeLastkhôngIList`1 dựa trên thuật toán đệm vòng giống như trong các câu trả lời của @Mark Byers và @MackieChan hơn nữa. Thật thú vị khi chúng giống nhau - Tôi đã viết của tôi hoàn toàn độc lập. Đoán thực sự chỉ có một cách để thực hiện bộ đệm vòng đúng cách. :-)

Nhìn vào câu trả lời của @ kbrimington, một kiểm tra bổ sung có thể được thêm vào để kiểm tra IQuerable<T>lại cách tiếp cận hoạt động tốt với Entity Framework - giả sử rằng những gì tôi có tại thời điểm này không có.


0

Bên dưới ví dụ thực tế làm thế nào để lấy 3 phần tử cuối cùng từ một bộ sưu tập (mảng):

// 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

Sử dụng phương pháp này để có được tất cả phạm vi mà không có lỗi

 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

Ít thực hiện khác nhau với việc sử dụng bộ đệm tròn. Các điểm chuẩn cho thấy phương thức này nhanh hơn hai lần so với phương pháp sử dụng Hàng đợi (triển khai TakeLast trong System.Linq ), tuy nhiên không phải không có chi phí - nó cần một bộ đệm phát triển cùng với số lượng phần tử được yêu cầu, ngay cả khi bạn có bộ sưu tập nhỏ bạn có thể nhận được phân bổ bộ nhớ lớn.

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];

    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.