1

I'm building a sample project using SignalR. The idea is that i have 3 different screens that shows a video, every 4 seconds the screen randomly changes and save the screen ID, the video ID and the timestamp as an object in a IEnumerable list. I want the list to store only the last 10 changes, so if a new change is stored in the first position, the oldest element needs to be removed; since there are multiple screens changing the list the elimination method can't be limited to delete the last element of the list, 2 screens could add something to the list at the same time and the list would be 12+ changes size.

How can i delete all the elements of the list that excedes the 10 changes limit?

This is the code related to the list.

private readonly ConcurrentDictionary<string, Cambio> _cambios = new 
ConcurrentDictionary<string, Cambio>();

 var cambios = new List<Cambio>
            {
                new Cambio{pantalla = "Sin cambios", video= "Sin cambios", 
                tStamp = DateTime.Now}
                //Sample
            };
            cambios.ForEach(cambio => 
               _cambios.TryAdd(cambio.tStamp.ToString(), cambio));

public class Cambio
{
    public string pantalla { get;set;}
    public string video { get; set; }
    public DateTime tStamp { get; set; }

}
Yantup
  • 72
  • 1
  • 10
  • I would not use a TimeStamp as a key for a dictionary. If you add the newest item to a list, it will be sorted as well. Use a while loop to remove the first, until the count = 10. – Jeroen van Langen Jan 24 '19 at 15:37
  • If this sample gets accepted the real project will have a more complex structure, in that case the dictionary Key will be something more appropiate. By the way, lets say that the real project could have 100 screens, a change is added to the queue and waits till the list lock is released. Doing the check in the while loop will affect the performance? @J.vanLangen – Yantup Jan 24 '19 at 15:56

5 Answers5

1

If you want to get every time the last 10 element added to the list you can do like this:

if(lst.Count>10)

lst = lst.ToArray().Reverse().Take(10);

if you want to take the 10 first element you can your directly Take(10)

Ahd Bk
  • 139
  • 5
0

I would suggest you to use Skip() and Take() methods to do that.

Example :

IEnumerable<int> data = new List<int>() { 1, 2, 3, 4 };

IEnumerable<int> newSubset = data.Skip(2).Take(2);

EDIT:

If you are inside a ForEach you cannot modify the iterating collection, wou will get a CollectionModifiedException so you have to project the items to a new List.

gatsby
  • 1,148
  • 11
  • 12
  • I don't know why I have a downvote here, can the author of the downvote explain me the reason? :) – gatsby Jan 24 '19 at 15:26
  • Just a side note: if your collection contains objects, you can modify the elements properties inside foreach. – Dimitar Jan 24 '19 at 15:26
  • Yes but no the number of elements, this is not a reason for a downvote. – gatsby Jan 24 '19 at 15:27
  • suppose that Ienumerable has more then 10 records. but we dont know how many records, how do you know how many record you are going to skip? (Keep in mind that op is asking for most recent 10 values) – Derviş Kayımbaşıoğlu Jan 24 '19 at 15:29
  • It seems to work, just one question to give you the accept. Using this methods will delete the remaining elements from the list or they just "copy/paste" the elements with the specificatiosn i give? – Yantup Jan 24 '19 at 15:30
  • Skip.Take does not delete while Stack deletes – Derviş Kayımbaşıoğlu Jan 24 '19 at 15:31
  • Creates a new list but the objects inside (if are reference objects) are the same, just creates a new list without the items you skipped. – gatsby Jan 24 '19 at 15:32
  • Helpfull, but no quite the thing i was looking for. Thanks anyway @gatsby – Yantup Jan 24 '19 at 15:34
0

You can create an Extension method:

    public static void AddNew<T>(this List<T> l, T item, int maxAmount)
    {

        l.Add(item);
        if (l.Count >= maxAmount)
        {
            l.RemoveRange(maxAmount, l.Count - maxAmount);
        }

    }

and then apply it to your list

Alex Sham
  • 489
  • 7
  • 14
  • This is exactly what i need – Yantup Jan 24 '19 at 15:34
  • Be aware if you do that inside the for each loop. You will get an exception. You have to do that outside the foreach. – gatsby Jan 24 '19 at 15:35
  • This is not safe but OP looks happy. – Derviş Kayımbaşıoğlu Jan 24 '19 at 15:37
  • im having a hard time trying to translate the idea but.. Ill call the method outside the loop, screens send info and store it on the list, then a validation for the 10 limit will be done calling this method. and save all the changes, i have a lock method(?) to queque the changes preventing multiple acces to the list at the same time. it doesnt seems to have any trouble yet – Yantup Jan 24 '19 at 15:39
0

Seems like a priority queue is what you need. https://en.m.wikipedia.org/wiki/Priority_queue

C# doesn’t have one built in but you could create one for your application yourself

https://visualstudiomagazine.com/articles/2012/11/01/priority-queues-with-c.aspx?m=1

https://www.codeproject.com/articles/13295/%2fArticles%2f13295%2fA-Priority-Queue-in-C

NGambit
  • 1,141
  • 13
  • 27
0

I want the list to store only the last 10 changes, so if a new change is stored in the first position, the oldest element needs to be removed;

You can use custom Queue with a limited size:

public class FixedSizedQueue<T> : ConcurrentQueue<T>
{
    private readonly object syncObject = new object();

    public int Size { get; private set; }

    public FixedSizedQueue(int size)
    {
        Size = size;
    }

    public new void Enqueue(T obj)
    {
        base.Enqueue(obj);
        lock (syncObject)
        {
            while (base.Count > Size)
            {
                T outObj;
                base.TryDequeue(out outObj);
            }
        }
    }
}

Please see more details here.

var queue = new FixedSizedQueue<Cambio>(10);
queue.Enqueue(new Cambio
{
    pantalla = "Sin cambios", 
    video= "Sin cambios", 
    tStamp = DateTime.Now
});
koryakinp
  • 3,989
  • 6
  • 26
  • 56