Sort list by string field of class - c#

I have a list of objects, each with time data, id numbers, and a string descriptor in the type field. I wish to pull all the values to the front of the list with a certain string type, while keeping the order of those list elements the same, and the order of the rest of the list elements the same, just attached to the back of those with my desired string.
I've tried, after looking for similar SE questions,
list.OrderBy(x => x.type.Equals("Auto"));
which has no effect, though all other examples I could find sorted by number rather than by a string.
List Objects class definition:
public class WorkLoad
{
public long id;
public DateTime timestamp;
...
public String type;
}
...create various workload objects...
schedule.Add(taskX)
schedule.OrderBy(x => x.type.Equals("Manual"));
//has no effect currently

If you already have a sorted list I think the fastest way to resort it by "having a type of auto or not" without losing the original order (and without having to resort all over again) could be this:
var result = list.Where(x => x.type.Equals("Auto"))
.Concat(list.Where(x => !x.type.Equals("Auto")))
.ToList();
Update:
You commented that "everyting else should be sorted by time", so you can simply do this:
var result = list.OrderByDescending(x => x.type.Equals("Auto"))
.ThenBy(x => x.Time).ToList();

You can use multiple orderings in a sequence:
list.OrderBy(x => x.type == "Auto" ? 0 : 1).ThenBy(x => x.type);

Related

How to filter a List<T> if it contains specific class data?

I need help with filtering list data in c#.
I got 3 class named Product.cs, Storage.cs and Inventory.cs.
public class Storage{
string StorageId;
string Name;
}
public class Inventory{
string InventoryId;
string StorageId;
string ProductId;
}
I got the filled List<Storage> mStorages, List<Product> mProduct and List<Inventory> mInventories.
I have trouble to print mStorages that contain with specific productId that only can be obtained from mInventories.
So, I tried this:
List<Storage> mFilteredStorage;
for(int i=0;i<mStorages.Count;i++){
if(mStorages[i] contain (productId from inventories)){
mFilteredStorage.add(mstorages[i]);
}
So I can get mFilteredStorage that contains specific product from inventories. (in inventories there are lot of product id).
What should I do to get that filteredStorage? I tried to use list.contains() but it only return true and at last there are duplicated storage at mFilteredStorage.
Really need your help guys. Thanks in advance.
I suggest you to read about lambda-expressions, that is what you are looking for.
mFilteredStorage.AddRange(mStorages.Where(storage => inventories.Any(inventory => inventory.productId == storage.productId)).ToList());
This returns you a list with your filtered conditions. So right after Where you iterate over each item in your list, I called this item storage. (you can name those what ever you want to) Then we iterate over your object inventories with another lambda expression. This, the second lambda expression, returns either true if any of inventories's productIds match the productId of the current iterating object of mStorages or false if they don't match.
So you once the productIds match you can imagine the code like the following:
mStorages.Where(storage => true);
And once the result of the second lambda expression is true, storage will be added to the IEnumerable you will get as a result of the Where method.
Since we get an IEnumerable as return, but we want to add those Storage objects to mFilteredStorage, I convert the IEnumerable to a list, by:
/*(the return object we get from the `Where` method)*/.ToList();
You can use LINQ to accomplish your goal. Since Storage has no ProductId, the query will match by StorageId.
var filteredStoragesQry =
from storage in mStorages
where inventories.Any(inventory => inventory.StorageId == storage.StorageId)
select storage;
mFilteredStorages = filteredStoragesQry.ToList();
This query is for LINQ to objects, but it will also work in Entity Framework, when you replace mStorages and inventories by the respective DbSet objects from the context.
mStorages.Join(mInventories, x => x.StorageId, y => y.StorageId, (x, y) => new { Storage = x, ProductId = y.ProductId})
.Where(z => z.ProductId == "specificProductId").Select(z => z.Storage).ToList()
I ended with this code.
mFilteredStorage = tempStorage.GroupBy(s => s.Id).Select(group => group.First()).ToList()
This code is what I want to show.

C# OrderByDescending on value from list in another list

I'm trying to order a list (will refer to this list as result) by a value in another list (Redeems).
The result list contains the Redeems list, and I want to order result by the field "SumChosenYear" in the "Redeems" list and get top 20. This is what I've managed to get, and in theory I think it should work.
result = result
.OrderByDescending(input => input.Redeems
.Select(input2 => input2.SumChosenYear)
.ToList())
.Take(20)
.ToList();
However it throws an exception saying "Atleast one object implement IComparable". Why does this happen?
This line:
input.Redeems
.Select(input2 => input2.SumChosenYear)
.ToList()
returns you a List and because List does not implement IComparable you cannot put this lambda inside this overload of OrderByDescending extension.
You have basically two options:
First option
Cretae your custom implementation of IComparer for this list (assuming SumChosenYear property is an int for this purpose):
public class SumChosenYearListComparer : IComparer<List<int>>
{
public int Compare(List<int> x, List<int> y)
{
//Your custom comparison...
}
}
and then use it with this overload of OrderByDescending extension:
var result = result
.OrderByDescending(input => input.Redeems
.Select(input2 => input2.SumChosenYear)
.ToList(), new SumChosenYearListComparer())
.Take(20)
.ToList();
Second option
You can choose which item in this list you want to use in the comparison(maybe the max, min, first or last value or maybe even the sum of all the items).
Assuming you want to comapre using the max value in the list your code could look something like this:
var result = result
.OrderByDescending(input => input.Redeems
.Max(input2 => input2.SumChosenYear))
.Take(20)
.ToList();

Lambda expression to return one result for each distinct value in list

I currently have a large list of a class object and I am currently using the following lambda function to return elements that meet the condition.
var call = callList.Where(i => i.ApplicationID == 001).ToList();
This will return a list of objects that all have an id of 001.
I am now curious as to what different ApplicationIDs there are. So I would like a lambda function that will look into this list and return a list where all the element have a different ApplicationID but only fetches one of those.
If i understand your question you can try:
var list = callList.GroupBy(x => x.ApplicationID).Select(x => x.First()).ToList();
So if you have a list like:
AppID:1, AppID:1, AppID:2, AppID:2, AppID:3, AppID:3
Will return:
AppID:1 AppID:2 AppID:3
You can use either First or FirstOrDefault to get back one result
var call = callList.First(i => i.ApplicationID == 001);
If no call exisrs with an ApplicationID of 001 this will throw an exception. If this may be expected consider using:
var call = callList.FirstOrDefault(i => i.ApplicationID == 001);
Here null will be returned if no such call exists and you can handle accordingly in you code.
To find out what other ApplicationId's exist you can query:
var Ids = callList.Where(i => i.ApplicationID != 001).Select(i => i.ApplicationID).Distinct();
You are saying
I am now curious as to what different ApplicationIDs there are. So I
would like a lambda function that will look into this list and return
a list where all the element have a different ApplicationID but only
fetches one of those.
I would suggest that is never something you'd actually want. You either don't care about the elements, you care about all of them, or you care about a specific one. There are few (none?) situations where you care about a random one from the list.
Without knowing about which specific one you care, I can't give you a solution for that version. Allesandro has given you a solution for the random one.
When you only care about the distinct ID's you would end up with
callList.Select(c => c.ApplicationID).Distinct()
which just gives you all ApplicationIDs.
if you care about all of them, you'd end up with
callList.GroupBy(c => c.ApplicationID)
this will give you an IEnumerable<IGrouping<String, Thingy>> (where Thingy is the type of whatever the type of elements of callList is.)
This means you now have a collection of ApplicationID -> collection of Thingy's. For each distinct ApplicationID you'll have a "List" (actually IEnumerable) of every element that has that ApplicationID
If you care for the Thingy of that - for example - has the lowest value of property Foo you would want
callList.GroupBy(c => c.ApplicationID)
.Select(group => group.OrderBy(thingy => thingy.Foo).First()))
here you first Group them by ApplicationID, and then for each list of thingies with the sample ApplicationID you Select the first one of them if you Order them by Foo
There is a way to use the Distinct in the query, but it makes you take care about the values equality. Let's assume your type is called CallClass and try:
class CallClass : IEqualityComparer<CallClass>
{
public int ApplicationId { get; set; }
//other properties etc.
public bool Equals(CallClass x, CallClass y)
{
return x.ApplicationId == y.ApplicationId;
}
public int GetHashCode(CallClass obj)
{
return obj.GetHashCode();
}
}
Now you're able to query values distinctly:
var call = callList.Distinct().ToList();

How to optimize a LINQ with minimum and additional condition

Asume we have a list of objects (to make it more clear no properties etc.pp are used)
public class SomeObject{
public bool IsValid;
public int Height;
}
List<SomeObject> objects = new List<SomeObject>();
Now I want only the value from a list, which is both valid and has the lowest height.
Classically i would have used sth like:
SomeObject temp;
foreach(SomeObject so in objects)
{
if(so.IsValid)
{
if (null == temp)
temp = so;
else if (temp.Height > so.Height)
temp = so;
}
}
return temp;
I was thinking that it can be done more clearly with LinQ.
The first approach which came to my mind was:
List<SomeObject> sos = objects.Where(obj => obj.IsValid);
if(sos.Count>0)
{
return sos.OrderBy(obj => obj.Height).FirstOrDefault();
}
But then i waas thinking: In the foreach approach i am going one time through the list. With Linq i would go one time through the list for filtering, and one time for ordering even i do not need to complete order the list.
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Can this be somehow optimized, so that the linw also only needs to run once through the list?
You can use GroupBy:
IEnumerable<SomeObject> validHighestHeights = objects
.Where(o => o.IsValid)
.GroupBy(o => o.Height)
.OrderByDescending(g => g.Key)
.First();
This group contains all valid objects with the highest height.
The most efficient way to do this with Linq is as follows:
var result = objects.Aggregate(
default(SomeObject),
(acc, current) =>
!current.IsValid ? acc :
acc == null ? current :
current.Height < acc.Height ? current :
acc);
This will loop over the collection only once.
However, you said "I was thinking that it can be done more clearly with LinQ." Whether this is more clear or not, I leave that up to you to decide.
You can try this one:
return (from _Object in Objects Where _Object.isValid OrderBy _Object.Height).FirstOrDefault();
or
return _Objects.Where(_Object => _Object.isValid).OrderBy(_Object => _Object.Height).FirstOrDefault();
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Only in the worst case scenario, where the first valid object is the last in order of obj.Height (or there is none to be found). Iterating the collection using FirstOrDefault will stop as soon as a valid element is found.
Can this be somehow optimized, so that the linw also only needs to run
once through the list?
I'm afraid you'd have to make your own extension method. Considering what I've written above though, I'd consider it pretty optimized as it is.
**UPDATE**
Actually, the following would be a bit faster, as we'd avoid sorting invalid items:
return object.Where(o => o.IsValid).OrderBy(o => o.Height).FirstOrDefault();

Detecting "near duplicates" using a LINQ/C# query

I'm using the following queries to detect duplicates in a database.
Using a LINQ join doesn't work very well because Company X may also be listed as CompanyX, therefore I'd like to amend this to detect "near duplicates".
var results = result
.GroupBy(c => new {c.CompanyName})
.Select(g => new CompanyGridViewModel
{
LeadId = g.First().LeadId,
Qty = g.Count(),
CompanyName = g.Key.CompanyName,
}).ToList();
Could anybody suggest a way in which I have better control over the comparison? Perhaps via an IEqualityComparer (although I'm not exactly sure how that would work in this situation)
My main goals are:
To list the first record with a subset of all duplicates (or "near duplicates")
To have some flexibility over the fields and text comparisons I use for my duplicates.
For your explicit "ignoring spaces" case, you can simply call
var results = result.GroupBy(c => c.Name.Replace(" ", ""))...
However, in the general case where you want flexibility, I'd build up a library of IEqualityComparer<Company> classes to use in your groupings. For example, this should do the same in your "ignore space" case:
public class CompanyNameIgnoringSpaces : IEqualityComparer<Company>
{
public bool Equals(Company x, Company y)
{
return x.Name.Replace(" ", "") == y.Name.Replace(" ", "");
}
public int GetHashCode(Company obj)
{
return obj.Name.Replace(" ", "").GetHashCode();
}
}
which you could use as
var results = result.GroupBy(c => c, new CompanyNameIgnoringSpaces())...
It's pretty straightforward to do similar things containing multiple fields, or other definitions of similarity, etc.
Just note that your defintion of "similar" must be transitive, e.g. if you're looking at integers you can't define "similar" as "within 5", because then you'd have "0 is similar to 5" and "5 is similar to 10" but not "0 is similar to 10". (It must also be reflexive and symmetric, but that's more straightforward.)
Okay, so since you're looking for different permutations you could do something like this:
Bear in mind this was written in the answer so it may not fully compile, but you get the idea.
var results = result
.Where(g => CompanyNamePermutations(g.Key.CompanyName).Contains(g.Key.CompanyName))
.GroupBy(c => new {c.CompanyName})
.Select(g => new CompanyGridViewModel
{
LeadId = g.First().LeadId,
Qty = g.Count(),
CompanyName = g.Key.CompanyName,
}).ToList();
private static List<string> CompanyNamePermutations(string companyName)
{
// build your permutations here
// so to build the one in your example
return new List<string>
{
companyName,
string.Join("", companyName.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
};
}
In this case you need to define where the work is going to take place i.e. fully on the server, in local memory or a mixture of both.
In local memory:
In this case we have two routes, to pull back all the data and just do the logic in local memory, or to stream the data and apply the logic piecewise. To pull all the data just ToList() or ToArray() the base table. To stream the data would suggest using ToLookup() with custom IEqualityComparer, e.g.
public class CustomEqualityComparer: IEqualityComparer<String>
{
public bool Equals(String str1, String str2)
{
//custom logic
}
public int GetHashCode(String str)
{
// custom logic
}
}
//result
var results = result.ToLookup(r => r.Name,
new CustomEqualityComparer())
.Select(r => ....)
Fully on the server:
Depends on your provider and what it can successfully map. E.g. if we define a near duplicate as one with an alternative delimiter one could do something like this:
private char[] delimiters = new char[]{' ','-','*'}
var results = result.GroupBy(r => delimiters.Aggregate( d => r.Replace(d,'')...
Mixture:
In this case we are splitting the work between the two. Unless you come up with a nice scheme this route is most likely to be inefficient. E.g. if we keep the logic on the local side, build groupings as a mapping from a name into a key and just query the resulting groupings we can do something like this:
var groupings = result.Select(r => r.Name)
//pull into local memory
.ToArray()
//do local grouping logic...
//Query results
var results = result.GroupBy(r => groupings[r]).....
Personally I usually go with the first option, pulling all the data for small data sets and streaming large data sets (empirically I found streaming with logic between each pull takes a lot longer than pulling all the data then doing all the logic)
Notes: Dependent on the provider ToLookup() is usually immediate execution and in construction applies its logic piecewise.

Categories

Resources