0

I do have a HashSet in C# which looks like following:

HashSet<List<int>> _hash = new HashSet<List<int>>();

Now, I have inserted a value into it as below:

_hash.add(new List<int> {1,7});

When I write following code after the code above:

_hash.contains(new List<int>{1,7});

I was expecting it to return true since the same value has been just added but it did return false. Which did confuse me. Moreover, how do I make sure when I do have a hashset of List then there are no duplicates in it before I add any new value to it.

I thought the entire reason behind using HashSet is to avoid any duplication but it seems that this one allows duplication.

Now, to put it into perspective, all I want is when I have a List> then how can I make sure that each element(List) going into List> is unique?

Lost
  • 12,007
  • 32
  • 121
  • 193
  • 2
    It is a new List with the same values. Its not the same list. If you want to check for values then HashSet> is not the right data structure. Tell us more about your use case. What are you trying to achieve? – Gerardo Grignoli Nov 09 '19 at 16:58
  • [how you expected to contains would return true?](https://dotnetfiddle.net/epoCVo) those 2 list are not having the same hash neither they are equal – Selvin Nov 09 '19 at 17:00
  • 6
    Remember, lists are mutable; those two lists have the same values *now*, but they might not a moment in the future, so they should not be treated as the same list. Gerardo is right; we need to get more information about what you are trying to do in order to advise you properly on the right way to solve your real problem. Putting mutable collections in a hash set and comparing them by value is a dangerous practice. – Eric Lippert Nov 09 '19 at 17:12
  • It seems like you picked the wrong data structure types for your problem. Each time you instantiate a new reference type it will be unique(in your case `List`). So your `Hashset` will might contain multiple unique `List` but with the same values in it – OlegI Nov 09 '19 at 17:21
  • @GerardoGrignoli All I want is I do have a List> and I want to make sure that each element List is unique inside List> – Lost Nov 09 '19 at 19:23
  • I updated my question with more details. Hope that helps clarify things. – Lost Nov 09 '19 at 19:26
  • I updated [my answer due](https://stackoverflow.com/a/58782256/880990) to your last edit. (scroll to the bottom) – Olivier Jacot-Descombes Nov 09 '19 at 20:21
  • @Lost HashSet is fast and stable. You can use List> or HashSet> too. You can use my answer with what you want. https://stackoverflow.com/questions/150750/hashset-vs-list-performance –  Nov 09 '19 at 21:10
  • 1
    What do you think about these two lists: `new List{1,2}`, `new List{2,1}`? Are they equal or not? – Theodor Zoulias Nov 09 '19 at 21:27

1 Answers1

1

You can create your own comparable read-only collection.

public class ComparableReadOnlyCollection<T> : ReadOnlyCollection<T>
{
    public ComparableReadOnlyCollection(IList<T> list)
        : base(list.ToArray())
    {
    }

    public override bool Equals(object other)
    {
        return
            other is IEnumerable<T> otherEnumerable &&
            otherEnumerable.SequenceEqual(this);
    }

    public override int GetHashCode()
    {
        int hash = 43;
        unchecked {
            foreach (T item in this) {
                hash = 19 * hash + item.GetHashCode();
            }
        }
        return hash;
    }
}

Note that ReadOnlyCollection<T> is just a wrapper for the original list. If you modify this list, the ReadOnlyCollection<T> reflects those changes. My implementation copies the original list to an array to make it really immutable.

But be aware that if the elements T are of a reference type, you can still modify members of the original objects! So be careful.

This test works as expected:

var hashSet = new HashSet<ComparableReadOnlyCollection<int>>();
hashSet.Add(new ComparableReadOnlyCollection<int>(new [] { 1, 7 }));

Console.WriteLine(hashSet.Contains(new ComparableReadOnlyCollection<int>(new [] { 1, 7 })));
Console.WriteLine(hashSet.Contains(new ComparableReadOnlyCollection<int>(new [] { 7, 1 })));
Console.WriteLine(hashSet.Contains(new ComparableReadOnlyCollection<int>(new [] { 1, 7, 0 })));

hashSet.Add(new ComparableReadOnlyCollection<int>(new [] { 1, 7 }));
hashSet.Add(new ComparableReadOnlyCollection<int>(new [] { 1, 7, 0 }));
hashSet.Add(new ComparableReadOnlyCollection<int>(new [] { 7, 1 }));
Console.WriteLine(hashSet.Count);
Console.ReadKey();

It prints

True
False
False
3

Note that it does not print 4, as there cannot be duplicates in the set.


2nd solution:

After reading your edit, I am not sure what you really want. Did you mean to create a HashSet<int> instead of a HashSet<List<int>> and to compare the elements of the lists instead of the lists themselves?

HashSet<int> _hash = new HashSet<int>(new List<int> { 1, 1, 2, 3, 5, 8, 13 });    

Now the hash set contains the numbers { 1, 2, 3, 5, 8, 13 }. Set elements are always unique.

You can then test

var hash2 = new HashSet<int> { 3, 8 };

if (_hash.IsSupersetOf(hash2)) {
    Console.WriteLine("_hash contains 3 and 8");
}

or, what is equivalent:

if (hash2.IsSubsetOf(_hash)) {
    Console.WriteLine("_hash contains 3 and 8");
}

3rd solution:

What about a List<HashSet<int>>? Because now, you can apply set operations to each element of the list (which is a hash set).

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • I think this code is complicated and not very pretty. –  Nov 09 '19 at 21:58
  • 1
    @OlivierRogier, the first solution consists only of the first code snippet. The second solution only of `if (_hash.IsSupersetOf(hash2))` or `if (hash2.IsSubsetOf(_hash))`. The rest are examples and test setups. `ComparableReadOnlyCollection` is a simple as it can and consists of the well known technique of overriding `Equals` and `GetHashCode`. You can't make it easier. – Olivier Jacot-Descombes Nov 10 '19 at 15:48