Finding the list of common objects between two lists - c#

I have list of objects of a class for example:
class MyClass
{
string id,
string name,
string lastname
}
so for example: List<MyClass> myClassList;
and also I have list of string of some ids, so for example:
List<string> myIdList;
Now I am looking for a way to have a method that accept these two as paramets and returns me a List<MyClass> of the objects that their id is the same as what we have in myIdList.
NOTE: Always the bigger list is myClassList and always myIdList is a smaller subset of that.
How can we find this intersection?

So you're looking to find all the elements in myClassList where myIdList contains the ID? That suggests:
var query = myClassList.Where(c => myIdList.Contains(c.id));
Note that if you could use a HashSet<string> instead of a List<string>, each Contains test will potentially be more efficient - certainly if your list of IDs grows large. (If the list of IDs is tiny, there may well be very little difference at all.)
It's important to consider the difference between a join and the above approach in the face of duplicate elements in either myClassList or myIdList. A join will yield every matching pair - the above will yield either 0 or 1 element per item in myClassList.
Which of those you want is up to you.
EDIT: If you're talking to a database, it would be best if you didn't use a List<T> for the entities in the first place - unless you need them for something else, it would be much more sensible to do the query in the database than fetching all the data and then performing the query locally.

That isn't strictly an intersection (unless the ids are unique), but you can simply use Contains, i.e.
var sublist = myClassList.Where(x => myIdList.Contains(x.id));
You will, however, get significantly better performance if you create a HashSet<T> first:
var hash = new HashSet<string>(myIdList);
var sublist = myClassList.Where(x => hash.Contains(x.id));

You can use a join between the two lists:
return myClassList.Join(
myIdList,
item => item.Id,
id => id,
(item, id) => item)
.ToList();

It is kind of intersection between two list so read it like i want something from one list that is present in second list. Here ToList() part executing the query simultaneouly.
var lst = myClassList.Where(x => myIdList.Contains(x.id)).ToList();

you have to use below mentioned code
var samedata=myClassList.where(p=>p.myIdList.Any(q=>q==p.id))

myClassList.Where(x => myIdList.Contains(x.id));

Try
List<MyClass> GetMatchingObjects(List<MyClass> classList, List<string> idList)
{
return classList.Where(myClass => idList.Any(x => myClass.id == x)).ToList();
}

var q = myClassList.Where(x => myIdList.Contains(x.id));

Related

Can I use LINQ to find out if a given property in a list repeats itself?

I have a list of objects that have a name field on them.
I want to know if there's a way to tell if all the name fields are unique in the list.
I could just do two loops and iterate over the list for each value, but I wanted to know if there's a cleaner way to do this using LINQ?
I've found a few examples where they compare each item of the list to a hard coded value but in my case I want to compare the name field on each object between each other and obtain a boolean value.
A common "trick" to check for uniqueness is to compare the length of a list with duplicates removed with the length of the original list:
bool allNamesAreUnique = myList.Select(x => x.Name).Distinct().Count() == myList.Count();
Select(x => x.Name) transforms your list into a list of just the names, and
Distict() removes the duplicates.
The performance should be close to O(n), which is better than the O(n²) nested-loop solution.
Another option is to group your list by the name and check the size of those groups. This has the additional advantage of telling you which values are not unique:
var duplicates = myList.GroupBy(x => x.Name).Where(g => g.Count() > 1);
bool hasDuplicates = duplicates.Any(); // or
List<string> duplicateNames = duplicates.Select(g => g.Key).ToList();
While you can use LINQ to group or create a distinct list, and then compare item-wise with the original list, that incurs a bit of overhead you might not want, especially for a very large list. A more efficient solution would store the keys in a HashSet, which has better lookup capability, and check for duplicates in a single loop. This solution still uses a little bit of LINQ so it satisfies your requirements.
static public class ExtensionMethods
{
static public bool HasDuplicates<TItem,TKey>(this IEnumerable<TItem> source, Func<TItem,TKey> func)
{
var found = new HashSet<TKey>();
foreach (var key in source.Select(func))
{
if (found.Contains(key)) return true;
found.Add(key);
}
return false;
}
}
If you are looking for duplicates in a field named Name, use it like this:
var hasDuplicates = list.HasDuplicates( item => item.Name );
If you want case-insensitivity:
var hasDuplicates = list.HasDuplicates( item => item.Name.ToUpper() );

In LINQ, do projections off an IOrderedEnumerable<T> preserve the order?

If I have an IOrderedEnumberable<Car>, I sort it and then do a projecting query...
is the order preserved in the projection?
For example, does this scenario work?
IOrderedEnumberable<Car> allCarsOrderedFastestToSlowest =
GetAllCars()
.OrderByDescending(car=>car.TopSpeed);
var top3FastestCarManufacturers =
allCarsOrderedFastestToSlowest
.Select(car=>car.Manufacturer)
.Distinct()
.Take(3);
Does the name of the top3FastestCarManufacturers variable convey the meaning of what has really happened in the code?
The documentation for the Distinct method doesn't say anything about whether the order is preserved or not. This is probably because it depends on the underlying implementation of the source.
You can use grouping to get the desired result, by getting the fastest car from each manufacturer, and then get the three fastest from that:
var topThreeFastestCarManufacturers =
GetAllCars()
.GroupBy(c => c.Manufacturer)
.Select(g => g.OrderByDescending(c => c.TopSpeed).First())
.OrderByDescending(c => c.TopSpeed)
.Take(3);
I suspect what is going to mess you up is the Distinct. This will likely reorder the results by manufacturer to produce the distinct results. I'd likely just iterate through the list until I had three distinct manufacturers.
The selection will retain the ordering but the remarks on Distinct indicate that it returns an unordered result set and that it is implementation dependent. To be sure, I wouldn't rely on it retaining the ordering and simply do it using the iteration.
var top3 = new List<string>();
foreach (var manufacturer in allCarsOrderedFastestToSlowest
.Select(car=>car.Manufacturer))
{
if (!top3.Contains(manufacturer))
{
top3.Add(manufacturer);
if (top3.Count == 3)
{
break;
}
}
}

Lambda expression for getting items from list according to indexes

Is it possible to get objects from a list according to their indexes at one shot? For example I have a List<string> alist. I have an integer List List<int> indexes which has indexes. Is it possible to get a result list from the original list which equals the indexes in the array.
I am looking for something like List<string> resultlist = alist.GetItems(items.indexin(indexes)) (that's just my imagination, not the actual syntax-sorry)
Something like this should work:
var result = indexes.Select(i => alist[i]).ToList();
Of course, I recommend you make it a bit more robust.
var result = alist.Where((theString, theIndex) => indexes.Contains(theIndex));
indexes.Select<int, string>(i => alist[i]).ToList<string>();

Sort one list by another

I have 2 list objects, one is just a list of ints, the other is a list of objects but the objects has an ID property.
What i want to do is sort the list of objects by its ID in the same sort order as the list of ints.
Ive been playing around for a while now trying to get it working, so far no joy,
Here is what i have so far...
//**************************
//*** Randomize the list ***
//**************************
if (Session["SearchResultsOrder"] != null)
{
// save the session as a int list
List<int> IDList = new List<int>((List<int>)Session["SearchResultsOrder"]);
// the saved list session exists, make sure the list is orded by this
foreach(var i in IDList)
{
SearchData.ReturnedSearchedMembers.OrderBy(x => x.ID == i);
}
}
else
{
// before any sorts randomize the results - this mixes it up a bit as before it would order the results by member registration date
List<Member> RandomList = new List<Member>(SearchData.ReturnedSearchedMembers);
SearchData.ReturnedSearchedMembers = GloballyAvailableMethods.RandomizeGenericList<Member>(RandomList, RandomList.Count).ToList();
// save the order of these results so they can be restored back during postback
List<int> SearchResultsOrder = new List<int>();
SearchData.ReturnedSearchedMembers.ForEach(x => SearchResultsOrder.Add(x.ID));
Session["SearchResultsOrder"] = SearchResultsOrder;
}
The whole point of this is so when a user searches for members, initially they display in a random order, then if they click page 2, they remain in that order and the next 20 results display.
I have been reading about the ICompare i can use as a parameter in the Linq.OrderBy clause, but i can’t find any simple examples.
I’m hoping for an elegant, very simple LINQ style solution, well I can always hope.
Any help is most appreciated.
Another LINQ-approach:
var orderedByIDList = from i in ids
join o in objectsWithIDs
on i equals o.ID
select o;
One way of doing it:
List<int> order = ....;
List<Item> items = ....;
Dictionary<int,Item> d = items.ToDictionary(x => x.ID);
List<Item> ordered = order.Select(i => d[i]).ToList();
Not an answer to this exact question, but if you have two arrays, there is an overload of Array.Sort that takes the array to sort, and an array to use as the 'key'
https://msdn.microsoft.com/en-us/library/85y6y2d3.aspx
Array.Sort Method (Array, Array)
Sorts a pair of one-dimensional Array objects (one contains the keys
and the other contains the corresponding items) based on the keys in
the first Array using the IComparable implementation of each key.
Join is the best candidate if you want to match on the exact integer (if no match is found you get an empty sequence). If you want to merely get the sort order of the other list (and provided the number of elements in both lists are equal), you can use Zip.
var result = objects.Zip(ints, (o, i) => new { o, i})
.OrderBy(x => x.i)
.Select(x => x.o);
Pretty readable.
Here is an extension method which encapsulates Simon D.'s response for lists of any type.
public static IEnumerable<TResult> SortBy<TResult, TKey>(this IEnumerable<TResult> sortItems,
IEnumerable<TKey> sortKeys,
Func<TResult, TKey> matchFunc)
{
return sortKeys.Join(sortItems,
k => k,
matchFunc,
(k, i) => i);
}
Usage is something like:
var sorted = toSort.SortBy(sortKeys, i => i.Key);
One possible solution:
myList = myList.OrderBy(x => Ids.IndexOf(x.Id)).ToList();
Note: use this if you working with In-Memory lists, doesn't work for IQueryable type, as IQueryable does not contain a definition for IndexOf
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();

List<T> Concatenation for 'X' amount of lists

I have for example 5 List all of the same type. Can I simply do
List<T> newset = List1.Concat(List2).Concat(List3).Concat(List4).....
You can do this (although you need .ToList() at the end).
However, it would be (slightly) more efficient to generate a single list, and use AddRange to add in each list. Just initialize the list with the total size of all of your lists, then call AddRange repeatedly.
You might want to consider doing something like:
public List<T> ConcatMultiple<T>(this List<T> list, params[] ICollection<T> others)
{
List<T> results = new List<T>(list.Count + others.Sum(i => i.Count));
results.AddRange(list);
foreach(var l in others)
results.AddRange(l);
return results;
}
Then calling via:
List<MyClass> newset = List1.ConcatMultiple(List2, List3, List4);
Yes, you can do that.
List<Thing> newSet = List1.Concat(List2).Concat(List3).Concat(List4).ToList();
If you want to concatenate an arbitrary (previously unknown) number of lists, then you may need to concatenate a collection of lists. Probably the easiest way to do this would be to use the SelectMany operator (or nested from clauses in LINQ query):
IEnumerable<List<int>> lists = /* get list of lists */;
List<int> result = lists.SelectMany(e => e).ToList();
The SelectMany operator calls the given function for every element of the input list (which is a list) and then concatenates all the resulting lists (the actual lists from your input list of lists). Alternatively using the LINQ query syntax:
List<int> result = (from l in lists
from e in l select e).ToList();
I believe that the C# compiler may actually optimize this, so that it doesn't iterate over all the individual elements (and does the same thing as the explicit version above). If you have a known number of lists, you can of course write:
List<int> result = (from l in new[] { list1, list2, list3, list4 }
from e in l select e).ToList();
It is not as elegant as defining your own method exactly for this purpose, but it shows how powerful the LINQ query syntax is.
you can, but do not forget to append .ToList(); in the end.
also you can call newset.AddRange(ListX); i think it is better in terms of performance
For variable list count:
IEnumerable<T> enumerable = Enumerable.Empty<T>();
foreach (List<T> list in [whatever])
enumerable = enumerable.Concat(list);
At the end you could add a "ToList()" if you want a rely List:
List<T> list = enumerable.ToList();
However, this might not be neeeded.
You certainly can do that, though it may not be incredibly efficient.
As stated by other answers, don't forget to add .ToList() to the end of your line of code, or use List1.AddRange(List2); List1.AddRange(List3); ... for added efficiency.
of you can use an union in LINQ if it is a real union that you want to do ofcourse...

Categories