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

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;
}
}
}

Related

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).
To recap, a list of Stores that has a list of Features
I have a second collection of DesiredFeatures (of type List<string> ).
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.
Here's what that looks like:
var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));
I know Intersect can help, and here's how I've used it:
var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));
This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.
The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.
Thank you!
You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.
var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));
It means take only those stores where desired features are all contained in features of the store.
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.
In other words, each desired feature must have a matching store feature.
I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:
var q = Stores.Where(s =>
DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);
A more efficient way could be to use a GroupJoin for performing the match:
var q = Stores.Where(s =>
DesiredFeatures.GroupJoin(s.Features,
df => df, sf => sf.Name, (df, sf) => sf.Any()
).All(match => match)
);
or Except to check for unmatched items:
var q = Stores.Where(s =>
!DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);
Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.
Updated Answer:
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());
or
var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());
Old Answer (if DesiredFeatures is a List<Feature>):
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());
Two things you want your code to perform.
var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) &&
s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
Ensure that every Feature is DesiredFeature
Store contains all Desired features.
Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

Select 1 column from a Group By LINQ query

I think what I need is relatively simple but every example I Google just returns results using First(), which I'm already doing. Here is my expression:
var options = configData.AsEnumerable().GroupBy(row => row["myColumn"]).Select(grp => grp.First());
What I need is only ONE column from the grp portion and to be able to suffix .ToList() on there without an error. As it stands I receive 4 columns, but only need a specific one, kind of like if this (grp => grp["myColumn"]), didn't result in error the Error 153 Cannot apply indexing with [] to an expression of type 'System.Linq.IGrouping<object,System.Data.DataRow>'
Also, Key does not work in the grouping portion as these results are from a DataTable object. See here - >
If you want only the keys, you can use
var options = configData.AsEnumerable().Select(row=>row["myColumn"]).Distinct();
I think that this is what you want:
configData.AsEnumerable()
.GroupBy(r => r["myColumn"])
.Select(g => new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
});
Do you understand how/what this does though? Try it out and inspect the resulting IEnumerable. I'm not sure you have a perfect understanding on how GroupBy works but take your time with above example.
See this part:
new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
}
This creates an anonymous type which outputs the values of "OtherColumn" column into a list grouped by "myColumn" where value of "myColumn" is in the myColumnValue property.
I'm not sure this answers your question but it looks like this is what you want.
The variable g is of the type IGrouping<object, DataRow>, it's not DataRow. The IGrouping interface is designed to provide a list of DataRow's grouped by object values - it does not produce a flat list, if it did then it would just be a Sort, not GroupBy.
Just specify the field you want after your call to First() e.g.
.Select(grp => grp.FirstOrDefault()["MyFieldName"]);
This will take the first record from the grouping and select the specified field from that record.

Finding the list of common objects between two lists

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));

Identify items in one list not in another of a different type

I need to identify items from one list that are not present in another list. The two lists are of different entities (ToDo and WorkshopItem). I consider a workshop item to be in the todo list if the Name is matched in any of the todo list items.
The following does what I'm after but find it awkward and hard to understand each time I revisit it. I use NHibernate QueryOver syntax to get the two lists and then a LINQ statement to filter down to just the Workshop items that meet the requirement (DateDue is in the next two weeks and the Name is not present in the list of ToDo items.
var allTodos = Session.QueryOver<ToDo>().List();
var twoWeeksTime = DateTime.Now.AddDays(14);
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
var matches = from wsi in workshopItemsDueSoon
where !(from todo in allTodos
select todo.TaskName)
.Contains(wsi.Name)
select wsi;
Ideally I'd like to have just one NHibernate query that returns a list of WorkshopItems that match my requirement.
I think I've managed to put together a Linq version of the answer put forward by #CSL and will mark that as the accepted answer as it put me in the direction of the following.
var twoWeeksTime = DateTime.Now.AddDays(14);
var subquery = NHibernate.Criterion.QueryOver.Of<ToDo>().Select(t => t.TaskName);
var matchingItems = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime &&
w.IsWorkshopItemInProgress == true)
.WithSubquery.WhereProperty(x => x.Name).NotIn(subquery)
.Future<WorkshopItem>();
It returns the results I'm expecting and doesn't rely on magic strings. I'm hesitant because I don't fully understand the WithSubquery (and whether inlining it would be a good thing). It seems to equate to
WHERE WorkshopItem.Name IS NOT IN (subquery)
Also I don't understand the Future instead of List. If anyone would shed some light on those that would help.
I am not 100% sure how to achieve what you need using LINQ so to give you an option I am just putting up an alternative solution using nHibernate Criteria (this will execute in one database hit):
// Create a query
ICriteria query = Session.CreateCriteria<WorkShopItem>("wsi");
// Restrict to items due within the next 14 days
query.Add(Restrictions.Le("DateDue", DateTime.Now.AddDays(14));
// Return all TaskNames from Todo's
DetachedCriteria allTodos = DetachedCriteria.For(typeof(Todo)).SetProjection(Projections.Property("TaskName"));
// Filter Work Shop Items for any that do not have a To-do item
query.Add(SubQueries.PropertyNotIn("Name", allTodos);
// Return results
var matchingItems = query.Future<WorkShopItem>().ToList()
I'd recommend
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime)
var allTodos = Session.QueryOver<ToDo>();
Instead of
var allTodos = Session.QueryOver<ToDo>().List();
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
So that the collection isn't iterated until you need it to be.
I've found that it's helpfull to use linq extension methods to make subqueries more readable and less awkward.
For example:
var matches = from wsi in workshopItemsDueSoon
where !allTodos.Select(it=>it.TaskName).Contains(wsi.Name)
select wsi
Personally, since the query is fairly simple, I'd prefer to do it like so:
var matches = workshopItemsDueSoon.Where(wsi => !allTodos.Select(it => it.TaskName).Contains(wsi.Name))
The latter seems less verbose to me.

How to group a IQueryable by property 1 but order by property 2?

Following from my question previously here
I used
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy((License => License.dateCreated)).OrderByDescending(lics => lics.Key).First();
To group the IQueryable
allEvaluationLicenses
by using License's property 1 which is 'dateCreated'
But now, how can I order them by using a different property such as 'nLicenceID'?
Is it possible to do something like this:
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy((License => License.dateCreated)).OrderByDescending(lics => (sort by nLicenseID here) ).First();
For LINQ-to-Objects, the objects inside each group retain the ordering in which they are discovered:
The IGrouping<TKey, TElement> objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping<TKey, TElement>. Elements in a grouping are yielded in the order they appear in source.
So: if your aim is to order the contents of each group, simply order the source:
var distinctAllEvaluationLicenses = allEvaluationLicenses
.OrderByDescending({whatever})
.GroupBy({etc}).First();
Note that this is not guaranteed to work for other LINQ sources, and note that it doesn't influence the order in which the groups are presented. To do that you could perhaps do something like:
var distinctAllEvaluationLicenses = allEvaluationLicenses
.GroupBy({etc}).
.OrderBy(grp => grp.Min(item => x.SomeProp)).First();
which would present the groups in order of the minimum SomeProp in each. Obviously adjust to max / etc as necessary.
To sort the items within the group you can use Select:
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy(License => License.dateCreated)
.Select(group => group.OrderByDescending(item => item.nLicenceID));

Categories