3

I need a set that is order which they were added just like a list.

The set may also be observable.

Any built.in set like this in .NET 4?

RayOldProf
  • 1,040
  • 4
  • 16
  • 40
  • 2
    @andrew.cuthbert a List can have a duplicate, i need a set – RayOldProf Sep 15 '15 at 14:45
  • 2
    A set isn't ordered, pretty fundamental restriction. If you want to keep order then you'll have to duplicate it in a List, after checking if already exists in the set. Removal is O(n) painful of course. – Hans Passant Sep 15 '15 at 14:48
  • @RayOldProf You can always use `List#Contains` to check for a duplicate before adding an item. But I don't think the specific behavior you're looking for is available built-in. – drew.cuthbert Sep 15 '15 at 14:48
  • You probably need to combine a set with a LinkedList for O(1) operations. Maybe one of the popular collection libs for .NET has such a thing built-in. – usr Sep 15 '15 at 14:49
  • 1
    A set that maintains insertion order is going to be expensive since the usual way to implement sets efficiently is to sort things internally. – Damien_The_Unbeliever Sep 15 '15 at 14:49
  • 1
    If an RDBMS can do this in O(log N) then in-memory structures can do it as well. This is not inefficient. – usr Sep 15 '15 at 14:54
  • Very similar questions: http://stackoverflow.com/questions/1552225/hashset-that-preserves-ordering http://stackoverflow.com/questions/9346526/what-is-the-equivalent-of-linkedhashset-java-in-c – Sebastian Negraszus Sep 15 '15 at 15:29

3 Answers3

1

As far as I know, there is no such type in .NET. I recently needed this and ended up implementing it myself; it's not that difficult.

The trick is to combine a Dictionary<T, LinkedListNode<T>> with a LinkedList<T>. Use the dictionary to query keys and values in O(1) time and the list to iterate in insertion-order. You need a dictionary instead of a set because you want to be able to call LinkedList<T>.Remove(LinkedListNode<T>) and not LinkedList<T>.Remove(T). The former has O(1) time complexity, the latter O(n).

Sebastian Negraszus
  • 11,915
  • 7
  • 43
  • 70
0

It sounds like you need ReadOnly Queue. In .Net we have built in Queue class but there is no built in ReadOnly Queue. To make sure there is no Duplicate value you can use contains check

There is one Nuget package which has ImmutableQueue. Not sure if it can help you. This creates new Queue object everytime when Enqueue or Dequeue operation is done. https://msdn.microsoft.com/en-us/library/dn467186(v=vs.111).aspx

Viru
  • 2,228
  • 2
  • 17
  • 28
0

I guess you could use a SortedDictionary<> and a Dictionary<> together to do this.

Assuming that you are never going to do more than int.MaxValue insertions into the set, you can use an integer "sequence number" as a key into a SortedDictionary that keeps track of the inserted items in insertion order.

Alongside this you need to use a Dictionary to map items to the sequence number that was used to insert them.

Putting this together into a class and a demo program: (NOT THREAD SAFE!)

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

namespace Demo
{
    public sealed class SequencedSet<T>: IEnumerable<T>
    {
        private readonly SortedDictionary<int, T> items = new SortedDictionary<int, T>();
        private readonly Dictionary<T, int> order = new Dictionary<T, int>();

        private int sequenceNumber = 0;

        public void Add(T item)
        {
            if (order.ContainsKey(item))
                return; // Or throw if you want.

            order[item] = sequenceNumber;
            items[sequenceNumber] = item;
            ++sequenceNumber;
        }

        public void Remove(T item)
        {
            if (!order.ContainsKey(item))
                return; // Or throw if you want.

            int sequence = order[item];

            items.Remove(sequence);
            order.Remove(item);
        }

        public bool Contains(T item)
        {
            return order.ContainsKey(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return items.Values.GetEnumerator();
        }

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

    internal class Program
    {
        private static void Main()
        {
            var test = new SequencedSet<string>();

            test.Add("One");
            test.Add("Two");
            test.Add("Three");
            test.Add("Four");
            test.Add("Five");

            test.Remove("Four");
            test.Remove("Two");

            foreach (var item in test)
                Console.WriteLine(item);
        }
    }
}

This should be fairly performant for insertions and deletions, but it will take double the memory of course. If you are doing a great number of insertions and deletions you could use a long instead of an int for the sequence numbering.

Unfortunately, if you are doing more than 2^63 deletions, even that won't work - although I would imagine that should be more than enough...

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276