[c#] باستخدام Linq للحصول على آخر عناصر N في مجموعة؟


Answers

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: أ) استخدام طريقة TakeLast () التي حددتها أعلاه يحل المشكلة ، ولكن إذا كنت تريد حقا أن تفعل ذلك دون طريقة إضافية ، فعليك فقط أن تدرك أنه في حين أن Enumerable.Reverse () يمكن أن يكون المستخدمة كطريقة للإضافة ، فأنت غير مطالب باستخدامها بهذه الطريقة:

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

بالنظر إلى المجموعة ، هل هناك طريقة للحصول على آخر عناصر N لهذه المجموعة؟ إذا لم تكن هناك طريقة في الإطار ، فما هي أفضل طريقة لكتابة طريقة تمديد للقيام بذلك؟




باستخدام هذه الطريقة للحصول على كل المدى دون خطأ

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






من غير الفعال أن نأخذ آخر 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});
}



وإليك هذه الطريقة التي تعمل على أي عدد لا يحصى ولكن يستخدم فقط التخزين المؤقت 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 الأخيرة.




إذا كان استخدام مكتبة طرف ثالث هو خيار ، فإن MoreLinq يعرف TakeLast() الذي يقوم بذلك بالضبط.




استخدم EnumerableEx.TakeLast في التجميع System.Interactive RX الخاص. إنه تطبيق O (N) مثل @ Mark ، ولكنه يستخدم قائمة انتظار بدلاً من بناء الحلقة المؤقتة (وعناصر dequeues عندما يصل إلى سعة المخزن المؤقت).

(ملاحظة: هذا هو الإصدار IEnumerable - وليس نسخة IObservable ، على الرغم من أن تنفيذ الاثنين متطابق إلى حد كبير)




ها هو الحل الخاص بي:

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 My TakeLast لـ IList`1 على نفس خوارزمية المخزن المؤقت الدائري كما في إجاباتMark Byers وMackieChan. من المثير للاهتمام كيف أنها مماثلة - كتبت لي بشكل مستقل تماما. أعتقد هناك حقا طريقة واحدة فقط للقيام مخزن مؤقت الحلقي بشكل صحيح. :-)

وبالنظر إلى إجابة kbrimington @ ، يمكن إضافة شيك إضافي إلى هذا من أجل IQuerable<T> إلى النهج الذي يعمل بشكل جيد مع Entity Framework - بافتراض أن ما لدي في هذه المرحلة لا.




Links



Tags

c# c#   linq