I want to check if one list contains all the elements of another list, for example:
(a,b,c,d) contains (c, a, d) = true
(a, b, c, d) contains (b, b, c, d) = false
I tried things like this:
static bool ContainsOther<T>(IEnumerable<T> a, IEnumerable<T> b)
{
return new HashSet<T>(a).IsSupersetOf(new HashSet<T>(b));
}
But then it won't solve this correctly:
(a, b, c, d) contains (b, b, c, d) = false
, it would say true
, but I would want to receive false
.
Same goes with nested loops.
CodePudding user response:
HashSet
is a collection which contains unique elements:
The HashSet class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
So the behavior is expected. A quick an dirty approach can be using dictionary to group and count elements:
static bool ContainsOther<T>(IEnumerable<T> a, IEnumerable<T> b)
{
var left = a.GroupBy(i => i)
.ToDictionary(g => g.Key, g => g.Count());
// or just cycle through elements of `b` and reduce count in a
var right = b.GroupBy(i => i)
.ToDictionary(g => g.Key, g => g.Count());
foreach (var (key, value) in right)
{
if (!left.TryGetValue(key, out var leftValue) || leftValue < value)
return false;
}
return true;
}
CodePudding user response:
So your enumerations can have duplicates and thus HashSet<T>
is not an option (it keeps unique items only); let's use Dictionary
instead (Key
is an item, Value
is its frequency):
static bool ContainsOther<T>(IEnumerable<T> left,
IEnumerable<T> right,
IEqualityComparer<T> comparer = default) {
if (ReferenceEquals(left, right))
return true;
if (left is null)
return false;
if (right is null)
return true;
comparer ??= EqualityComparer<T>.Default;
// left items with their frequencies
var dict = left
.GroupBy(item => item, comparer)
.ToDictionary(group => group.Key, group => group.Count(), comparer);
// do we have an item in right which is not in dict with at least frequency?
foreach (var item in right)
if (dict.TryGetValue(item, out int count) && count > 0)
dict[item] = count - 1;
else
return false;
return true;
}
CodePudding user response:
This works for me:
static bool ContainsOther<T>(IEnumerable<T> a, IEnumerable<T> b) =>
(
from x in a.ToLookup(_ => _)
join y in b.ToLookup(_ => _) on x.Key equals y.Key
from z in x.Zip(y)
select z
).Count() == b.Count();
It matches the values and ensures that the number of items for each key have the same length as in b
.
Console.WriteLine(ContainsOther(new[] { 'a', 'b', 'c', 'd', }, new[] { 'c', 'a', 'd', }));
Console.WriteLine(ContainsOther(new[] { 'a', 'b', 'c', 'd', }, new[] { 'b', 'b', 'c', 'd', }));
Gives True
and False
as required.
CodePudding user response:
I would go with the two simple approaches.
List<string> ls1 = new List<string>() { "a", "b", "c" , "d"};
List<string> ls2 = new List<string>() { "c", "a", "y" };
bool isSuperset1 = ls1.Intersect(ls2).Count() == ls2.Count;
bool isSuperset2 = !ls2.Except(ls1).Any();