I'm trying to write a method for a project which takes any number of lists as parameters, and returns a new list containing terms which ALL of those lists share. I have functional code, but I'd much prefer to use the params keyword rather than having to create a list of lists which holds all the lists I want to compare.
static List<T> Shared<T>(List<T> first, List<T> second)
{
List<T> result = new List<T>();
foreach (T item in first)
if (second.Contains(item) && !result.Contains(item)) result.Add(item);
return result;
}
static List<T> Shared<T>(List<List<T>> lists)
{
List<T> result = lists.First();
foreach (List<T> list in lists.Skip(1))
{
result = Shared<T>(result, list);
}
return result;
}
Is my current code, which works fine comparing two lists, but in order to compare more than two lists I have to either create a new list like:
List<int> nums1 = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> nums2 = new List<int> { 1, 2, 3 };
List<int> nums3 = new List<int> { 6, 5, 3, 2 };
List<int> listOfLists = Shared<int>(new List<List<int>> {nums1, nums2, nums3});
foreach (int item in listOfLists)
Console.WriteLine(item);
//Writes 2 and 3
etc. I would really wish to just be able to use Shared(list1, list2, list3, list4...) instead, even if this code is already somewhat functional. Currently any attempts to use a params version complains that "No overload for method 'Shared' takes N arguments"
Also I know my code could probably be done more efficiently, so I'd be glad to see suggestions on that too but primarily I need to get my head around why using params isn't working - if it's even possible.
Are you looking for this?
static List<T> Shared<T>(params List<T>[] lists)
The params parameter must always have an array type, but it can be an array of Lists.
It can be done quiet easily:
using System.Linq;
// ..
static List<T> Shared<T>(params List<T>[] lists)
{
if (lists == null)
{
throw new ArgumentNullException("lists");
}
return Shared(lists.ToList());
}
Building on the response of #Selman22 who proposed the method signature, you could alternatively use this LINQ query, to achieve the desired result.
static List<T> Shared<T>(params List<T>[] lists)
{
return
lists.Skip(1).Aggregate( // Skip first array item, because we use it as a seed anyway
lists.FirstOrDefault(), // Seed the accumulator with first item in the array
(accumulator, currentItem) => accumulator.Intersect(currentItem).ToList()); // Intersect each item with the previous results
}
We skip the first item that is being used as the seed for the accumulator, and do an intersect with the accumulator for each item in the given params array, since only the items that are contained in ALL the lists are kept in the accumulator result.
To test it out, you can use
Shared(nums1, nums2, nums3).ForEach(r => Console.WriteLine(r));
Related
I want to write a function that processs two Lists of the same objects. The function does always the same thing:
Find the objects that are only in List2 but not in List1 -> Do something with them
Find the object that are in both Lists -> Do something different with them.
Now the point is, that I have List pairs holding different kind of objects to which I want to apply this exact process.
Example:
List<Foo1> L11, L12;
List<Foo2> L21, L22;
List<Foo3> L31, L32;
So how do I have to write the code, so that I do not have to repeat the code for each List type ?
Greetings and Thank you
I would prepare a method, like below:
static void Process<T>(IEnumerable<T> list1, IEnumerable<T> list2, Action<T> onlyIn2, Action<T> inBoth)
{
var hash = new HashSet<T>(list1);
foreach (var item2 in list2)
if (hash.Contains(item2))
inBoth(item2);
else
onlyIn2(item2);
}
You can then use it as follows:
var list1 = new List<int> {1, 2, 3, 4, 5};
var list2 = new List<int> {3, 4, 5, 6};
Process(list1, list2, a =>
{
Console.WriteLine("{0} only in 2", a);
}, a =>
{
Console.WriteLine("{0} in both", a);
});
Note that it uses standard comparison rules (for objects reference equality unless Equals is overrided or IEqualityComparer<TKey> is implemented).
LINQ already provides two methods which do this:
// get all members of L11 not present in L12
var except = L11.Except(L12).ToList();
// get members present in both lists
var intersect = L11.Intersect(L12).ToList();
These overloads will use the default comparer for the list element type, so since you want to compare custom classes, you will need to use the overload which accepts a custom IEqualityComparer<T>:
var comparer = new CustomComparer();
var except = L11.Except(L12, comparer).ToList();
var intersect = L11.Intersect(L12, comparer).ToList();
which you need to write yourself:
class CustomComparer : IEqualityComparer<SomeClass>
{
public bool Equals(SomeClass x, SomeClass y)
{
// return true if equal
}
public int GetHashCode(SomeClass obj)
{
// return a hash code for boj
}
}
Your can use the Except/Intersect Linq methods as follows:
void Process<T>(IList<T> list1, IList<T> list2, IEqualityComparer<T> comparer = null) {
//Find the objects that are only in List2 but not in List1
foreach(var item in list2.Except(list1, comparer)) {
// -> Do something with them
}
//Find the object that are in both Lists -> Do something different with them.
foreach(var item in list1.Intersect(list2, comparer)) {
// -> Do something different with them.
}
}
If i have a list of strings, what is the best way to determine if every element in another list is contains in this list. For example:
List<string> list = new List<string>();
list.Add("Dog");
list.Add("Cat");
list.Add("Bird");
List<string> list2 = new List<string>();
list.Add("Dog");
list.Add("Cat");
if (list.ContainsList(list2))
{
Console.Write("All items in list2 are in list1")
}
I am trying to determine if there something like this "ContainsList" method?
if (!list2.Except(list).Any())
Loved SLaks version. Just for completeness, you can use HashSet method IsSubsetOf when performing set operations (also check IsSupersetOf method). There are pros and cons for this approach. Next code shows an example:
var list1 = new HashSet<string>{ "Dog", "Cat", "Bird" };
var list2 = new HashSet<string>{ "Dog", "Cat" };
if (list2.IsSubsetOf(list1))
{
Console.Write("All items in list2 are in list1");
}
Except method is streaming in nature. In query list2.Except(list1) list1 is buffered completely into memory, and you iterate one item at a time through list2. IsSubsetOf works eagerly in the opposite manner. This starts to make a difference when you have huge sets of data.
To analyse the worst case performance, here is some code from Except implementation at Monos Enumerable (dotPeek gives very similar results, just less readable)
var items = new HashSet<TSource> (second, comparer); //list1.Count
foreach (var element in first) //list2.Count
if (items.Add (element)) //constant time
yield return element;
as result O(list1.Count + list2.Count), loops aren't nested.
IsSubset has next method call, if second IEnumerable is HashSet (decompiled via dotPeek):
private bool IsSubsetOfHashSetWithSameEC(HashSet<T> other)
{
foreach (T obj in this) //list2.Count
if (!other.Contains(obj)) //constant time
return false;
return true;
}
Resulting in O(list2.Count) if list1 is a HashSet.
How about,
var list1 = new List<string>{"Dog","Cat","Bird"};
var list2 = new List<string>{"Dog","Cat"};
if (list1.Union(list2).SequenceEqual(list1))
Console.Write("All items in list2 are in list1");
How about this
list1.intersect (list2).ToList ().Foreach ((x)=>
{
Console.Writeline (x)
});
With a list you can do:
list.AddRange(otherCollection);
There is no add range method in a HashSet.
What is the best way to add another ICollection to a HashSet?
For HashSet<T>, the name is UnionWith.
This is to indicate the distinct way the HashSet works. You cannot safely Add a set of random elements to it like in Collections, some elements may naturally evaporate.
I think that UnionWith takes its name after "merging with another HashSet", however, there's an overload for IEnumerable<T> too.
This is one way:
public static class Extensions
{
public static bool AddRange<T>(this HashSet<T> source, IEnumerable<T> items)
{
bool allAdded = true;
foreach (T item in items)
{
allAdded &= source.Add(item);
}
return allAdded;
}
}
You can also use CONCAT with LINQ. This will append a collection or specifically a HashSet<T> onto another.
var A = new HashSet<int>() { 1, 2, 3 }; // contents of HashSet 'A'
var B = new HashSet<int>() { 4, 5 }; // contents of HashSet 'B'
// Concat 'B' to 'A'
A = A.Concat(B).ToHashSet(); // Or one could use: ToList(), ToArray(), ...
// 'A' now also includes contents of 'B'
Console.WriteLine(A);
>>>> {1, 2, 3, 4, 5}
NOTE: Concat() creates an entirely new collection. Also, UnionWith() is faster than Concat().
"... this (Concat()) also assumes you actually have access to the variable referencing the hash set and are allowed to modify it, which is not always the case." – #PeterDuniho
This is probable a common question, and I have searched other question without finding a solution that works (note, my skill in C# and linq is limited - so a simple solution would be appreciated!).
Here is the issue:
I have 2 lists with objects. I want to compare them and return all the NEW objects in list2.
Example:
ObjectList List1; // contains 3 objects that is stored in the database
ObjectList List2; // contains the same 3 objects as in List1 and a new object that was added from a webpage (the parent object was updated on the webpage)
ObjectList List3; // should do a compare of List1 and List2, and return the NEW objects in List2 (so the result should only be Object number 4)
Note:
The order does not matter. I only want the new object(s)
Normally objects are only added to List2. IF any object is removed (compare to List1), then this should be ignored. (so object that only exists in List1 is of no interest)
Thanks for any suggestions or links to previously questions i missed in my search
Edit
Here is a small example of first try with Except (this returned an error)
I have shortened it a bit. The method is from our software, so they are probable not know to you. Sorry about that.
// caDialogObjects = List1 (caDialogQLMLinks is the link to the objects)
RepositoryObjectList caDialogObjects = args.Object.GetConfiguration().GetObjectSet(caDialogQLMLinks);
// caObjectObjects = List2 (caObjectQLMLinks is the link to the objects)
RepositoryObjectList caObjectObjects = args.Object.GetConfiguration().GetObjectSet(caObjectQLMLinks);
// List 3
RepositoryObjectList caTotal;
caTotal = caObjectObjects.Except(caDialogObjects);
Solution that worked
The Exception did not work since the list is just a reference (not a value). It is possible to use the second parameter, but i got a linq code that worked:
RepositoryObjectList caNewCA =
new RepositoryObjectList(caDialogObjects.Where(item1 =>
!caObjectObjects.Any(item2 => item1.Id == item2.Id)));
Use this:
var list3 = list2.Except(list1);
This uses the Except extension method which returns all elements in list2 that are not in list1.
It is important to note, that Except returns an IEnumerable<T> where T is the type of the object inside list1 and list2.
If you need your list3 to be of a specific type, you need to convert that return value. In the simplest case, your target type has a constructor that can handle and IEnumerable<T>:
RepositoryObjectList list3 = new RepositoryObjectList(list2.Except(list1));
List<MyObject> objectList1 = new List<MyObject>();
List<MyObject> objectList2 = new List<MyObject>();
List<MyObject> objectList3 = objectList2.Where(o => !objectList1.Contains(o)).ToList();
This should do it, as long as MyObject is IComparable
http://msdn.microsoft.com/en-us/library/system.icomparable.aspx
Here's an example of how you could do it using LINQ:
List<int> l1 = new List<int>();
List<int> l2 = new List<int>();
l1.AddRange(new int[] {1, 2, 3, 5});
l2.AddRange(new int[] {1, 2, 3, 4});
// get only the objects that are in l2, but not l1
var l3 = l2.Except(l1);
The third list will only contain one element, 4.
secondList.Except(firstList)
which uses
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second
)
http://msdn.microsoft.com/en-us/library/bb300779.aspx
or
secondList.Except(firstList,new CustomComparer())
which uses
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second,
IEqualityComparer<TSource> comparer
)
http://msdn.microsoft.com/en-us/library/bb336390.aspx
I'd like to use Remove() method on list of lists, but it's not working for me.
Simple example should say everything:
List<List<int>> list = new List<List<int>>();
list.Add(new List<int> { 0, 1, 2 });
list.Add(new List<int> { 1, 2 });
list.Add(new List<int> { 4 });
list.Add(new List<int> { 0, 1, });
list.Remove(new List<int> { 1, 2 });
If I use RemoveAt(1) it works fine but Remove() not.
It is obviously the same reason that this code returns false:
List<int> l1 = new List<int>();
List<int> l2 = new List<int>();
l1.Add(1);
l2.Add(1);
bool b1 = l1 == l2; // returns False
bool b2 = l1.Equals(l2); // returns False too
So it seems to me that I cannot simply compare two lists or even arrays. I can use loops instead of Remove(), but there must be easier way.
Thanks in advance.
The problem is that List<T> doesn't override Equals and GetHashCode, which is what List<T> will use when trying to find an item. (In fact, it will use the default equality comparer, which means it'll use the IEquatable<T> implementation if the object implements it, and fall back to object.Equals/GetHashCode if necessary). Equals will return false as you're trying to remove a different object, and the default implementation is to just compare references.
Basically you'd have write a method to compare two lists for equality, and use that to find the index of the entry you want to remove. Then you'd remove by index (using RemoveAt). EDIT: As noted, Enumerable.SequenceEqual can be used to compare lists. This isn't as efficient as it might be, due to not initially checking whether the counts are equal when they can be easily computed. Also, if you only need to compare List<int> values, you can avoid the virtual method call to an equality comparer.
Another alternative is to avoid using a List<List<int>> in the first place - use a List<SomeCustomType> where SomeCustomType includes a List<int>. You can then implement IEquatable<T> in that type. Note that this may well also allow you to encapsulate appropriate logic in the custom type too. I often find that by the type you've got "nested" collection types, a custom type encapsulates the meaning of the inner collection more effectively.
First approach:
List<int> listToRemove = new List<int> { 1, 2 };
list.RemoveAll(innerList => innerList.Except(listToRemove).Count() == 0);
This also removes the List { 2, 1 }
Second approach (preferred):
List<int> listToRemove = new List<int> { 1, 2 };
list.RemoveAll(innerList => innerList.SequenceEqual(listToRemove));
This removes all lists that contain the same sequence as the provided list.
List equality is reference equality. It won't remove the list unless it has the same reference as a list in the outer list. You could create a new type that implements equality as set equality rather than reference equality (or you do care about order as well?). Then you could make lists of this type instead.
This simply won't work because you're tying to remove a brand new list (the new keyword kind of dictates such), not one of the ones you just put in there. For example, the following code create two different lists, inasmuch as they are not the same list, however much they look the same:
var list0 = new List<int> { 1, 2 };
var list1 = new List<int> { 1, 2 };
However, the following creates one single list, but two references to the same list:
var list0 = new List<int> { 1, 2 };
var list1 = list0;
Therefore, you ought to keep a reference to the lists you put in there should you want to act upon them with Remove in the future, such that:
var list0 = new List<int> { 1, 2 };
listOfLists.Remove(list0);
They are different objects. Try this:
List<int> MyList = new List<int> { 1, 2 };
List<List<int>> list = new List<List<int>>();
list.Add(new List<int> { 0, 1, 2 });
list.Add(MyList);
list.Add(new List<int> { 4 });
list.Add(new List<int> { 0, 1, });
list.Remove(MyList);
You need to specify the reference to the list you want to remove:
list.Remove(list[1]);
which, really, is the same as
list.RemoveAt(1);