How to use LINQ to sort custom items by using StartWith - c#

I have the following code. which is based on using a temp container to select specific items and then add them at the end of the list.
var allRoles = roles.Table
.AsEnumerable().Select(p => new FirmRole
{
Code = p.Field<string>("RoleName"),
Name = p.Field<string>("RoleName")
})ToList();
var formRoles = allRoles.Where(p => p.Code.StartsWith("f")).ToList();
var otherRoles = allRoles.Except(formRoles).ToList();
otherRoles.AddRange(formRoles);
Would it be a better way to shorten this code and get rid of the temp list?
Something like
var allRoles = roles.Table
.AsEnumerable().Select(p => new FirmRole
{
Code = p.Field<string>("RoleName"),
Name = p.Field<string>("RoleName")
}).OrderBy(x=>x.Code.StartsWith("f")).ThenBy(a=>a);

On IEnumerable<T> (as is in this case) you are right, because OrderBy is a stable sorting (see Enumerable.OrderBy: This method performs a stable sort; that is, if the keys of two elements are equal, the order of the elements is preserved., so for elements with the same key, their previous ordering is maintained. On IQueryable<T> this isn't guaranteed.
var allRoles = roles.Table
.AsEnumerable().Select(p => new FirmRole
{
Code = p.Field<string>("RoleName"),
Name = p.Field<string>("RoleName")
}).Distinct()
.OrderBy(x => x.Item.Code.StartsWith("f"))
.ToList();
Note that you don't need a secondary ordering, because OrderBy is, as I've said, stable.
Speedwise: you'll have to benchmark it with small and big sets. An OrderBy should be O(nlogn), but an ordering by true/false (as in this case) is probably more similar to O(n)

The second example reads better.
Don't think you need the .ToList() after Distinct().
Hope that helps

You should use GroupBy and ToLookup to get the results that you are looking for.
var allRoles = roles.Table
.AsEnumerable().Select(p => new FirmRole
{
Code = p.Field<string>("RoleName"),
Name = p.Field<string>("RoleName")
}).GroupBy(x => x.StartsWith("f")).ToLookup(g => g.Key);;
var formRoles = allRoles[true].ToList();
var otherRoles = allRoles[false].ToList();

I believe that you can implement a custom sort comparison class based on the IComparer<T> interface for providing the required custom sorting:
public class CustomSortComparer : IComparer<string>
{
public int Compare(string x, string y)
{
StringComparer sc = StringComparer.CurrentCultureIgnoreCase;
if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y))
{
return sc.Compare(x, y);
}
if (x.StartsWith("f", StringComparison.CurrentCultureIgnoreCase) &&
!y.StartsWith("f", StringComparison.CurrentCultureIgnoreCase))
{
return 1;
}
if (y.StartsWith("f", StringComparison.CurrentCultureIgnoreCase) &&
!x.StartsWith("f", StringComparison.CurrentCultureIgnoreCase))
{
return -1;
}
return sc.Compare(x, y);
}
}
And then call:
var allRoles = roles.Table
.AsEnumerable().Select(p => new FirmRole
{
Code = p.Field<string>("RoleName"),
Name = p.Field<string>("RoleName")
}).OrderBy(x => x.Code, new CustomSortComparer()).ToList();

Related

Check if elements from one list elements present in another list

I have 2 c# classes -
class ABC
{
string LogId;
string Name;
}
class XYZ
{
string LogId;
string Name;
}
class Checker
{
public void comparelists()
{
List<ABC> lstABC =new List<ABC>();
lstABC.Add(new ABC...);
lstABC.Add(new ABC...);
lstABC.Add(new ABC...);
List<XYZ> lstXYZ =new List<XYZ>();
lstXYZ.Add(new XYZ...);
lstXYZ.Add(new XYZ...);
lstXYZ.Add(new XYZ...);
var commonLogId = lstABC
.Where(x => lstXYZ.All(y => y.LogId.Contains(x.LogId)))
.ToList();
}
}
As seen from the code , I want to fetch all logids from lstABC which are present in lstXYZ.
Eg. lstABC has ->
LogId="1", Name="somename1"
LogId="2", Name="somename2"
LogId="3", Name="somename3"
LogId="4", Name="somename4"
LogId="5", Name="somename5"
lstXYZ has ->
LogId="1", Name="somename11"
LogId="2", Name="somename22"
LogId="3", Name="somename33"
LogId="8", Name="somename8"
LogId="9", Name="somename9"
Then all logids from lstABC which are present in lstXYZ are - 1,2,3 ; so all those records are expected to get fetched.
But with below linq query -
var commonLogId = lstABC
.Where(x => lstXYZ.All(y => y.LogId.Contains(x.LogId)))
.ToList();
0 records are getting fetched/selected.
approach with Any()
var res = lstABC.Where(x => (lstXYZ.Any(y => y.LogId == x.LogId))).Select(x => x.LogId);
https://dotnetfiddle.net/jRnUwS
another approach would be Intersect() which felt a bit more natural to me
var res = lstABC.Select(x => x.LogId).Intersect(lstXYZ.Select(y => y.LogId));
https://dotnetfiddle.net/7iWYDO
You are using the wrong LINQ function. Try Any():
var commonLogId = lstABC
.Where(x => lstXYZ.Any(y => y.LogId == x.LogId))
.ToList();
Also note that the id comparison with Contains() was wrong. Just use == instead.
All() checks if all elements in a list satisfy the specified condition. Any() on the other hand only checks if at least one of the elements does.
Be aware that your implementation will be very slow when both lists are large, because it's runtime complexity grows quadratically with number of elements to compare. A faster implementation would use Join() which was created and optimized exactly for this purpose:
var commonLogIds = lstABC
.Join(
lstXYZ,
x => x.LogId, // Defines what to use as key in `lstABC`.
y => y.LogId, // Defines what to use as key in `lstXYZ`.
(x, y) => x) // Defines the output of matched pairs. Here
// we simply use the values of `lstABC`.
.ToList();
It seems pretty unnatural to intersect entirely different types, so I would be tempted to interface the commonality and write an EqualityComparer:
class ABC : ILogIdProvider
{
public string LogId {get;set;}
public string Name;
}
class XYZ : ILogIdProvider
{
public string LogId{get;set;}
public string Name;
}
interface ILogIdProvider
{
string LogId{get;}
}
class LogIdComparer : EqualityComparer<ILogIdProvider>
{
public override int GetHashCode(ILogIdProvider obj) => obj.LogId.GetHashCode();
public override bool Equals(ILogIdProvider x, ILogIdProvider y) => x.LogId == y.LogId;
}
Then you can Intersect the lists more naturally:
var res = lstABC.Intersect(lstXYZ, new LogIdComparer());
Live example: https://dotnetfiddle.net/0Tt6eu

Sorting a list using reflection to pass a member name by string

I have a and mini class and a List<T> thereof.
List<mini> result;
public class mini
{
public long SN;
public int PlayTime;
public string Date;
public int Score;
}
//The value is the Name of SN, PlayTime, Date, Score
string sortColumn;
string sortColumnDir; //asc, desc value
The caller can specify a member name to sort on, and I try to sort on the result value:
if("SN" == sortColumn)
var sortresult = result.OrderBy(c => c.SN).ToList<miniCompletion>();
else if("PlayTime" == Date)
var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>();
else if("PlayTime" == sortColumn)
var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>();
else if("Score" == sortColumn)
var sortresult = result.OrderBy(c => c.Score).ToList<miniCompletion>();
But this code is too inefficient, because it involves a lot of copy-pasting nearly duplicate code. And for sorting descendingly, the code doubles in size.
So I tried:
var sortresult = result.OrderBy(c => c.GetType().GetMember(sortColumn)[0].Name).ToList();
But the sort failed.
The sort fails because you're sorting by the property name, not its value. The name is of course equal for all items.
You need to get the value. Use GetField(...).GetValue(c). If you can't use GetField() (or you'd rather even use properties, not fields, so GetProperty()), see How do I get the value of MemberInfo?.
You can build your sorting lambda dynamically with linq expressions. Try this code:
var parameter = Expression.Parameter(typeof(mini), "c");
var member = Expression.PropertyOrField(parameter, sortColumn);
var cast = Expression.Convert(member, typeof(IComparable));
var lambda = Expression.Lambda<Func<mini, IComparable>>(cast, parameter);
now lambda is an expression like c => (IComparable)c.Date and can be compiled to Func<mini, IComparable>:
var func = lambda.Compile();
At this moment you can sort your result:
var sortedResult = sortColumnDir == "ASC"
? result.OrderBy(func)
: result.OrderByDescending(func);
You can find demo here
Please note, that this code is working because all fields in mini are implementing IComparable interface
Change this:
if("SN" == sortColumn) var sortresult = result.OrderBy(c => c.SN).ToList<miniCompletion>(); else if("PlayTime" == Date) var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>(); else if("PlayTime" == sortColumn) var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>(); else if("Score" == sortColumn) var sortresult = result.OrderBy(c => c.Score).ToList<miniCompletion>();
With enums for asc, desc and sortColumn like:
If(SortComun.SN == sortColumn)
Int comparison is faster and enums are cleaner. Also, you can try result.AsParallel().OrderBy() if you have too much registers.
About your second approach, try:
var sortResult = result.OrderBy(c => c.GetType().GetProperty(sortColumn).GetValue(c)).ToList();
You can test which approach suits you better. Having reflection as the most generic answer, the other one could be faster in an intensive environment.
If you test and share your tests result could be awesome.

Compare two List elements and replace if id is equals

I have two lists with Classes
public class Product
{
int id;
string url;
ect.
}
I need compare in the old list (10k+ elements) a new list(10 elements) by ID
and if an id is same just replace data from new List to old list
I think it will be good using LINQ.
Can you help me how can I use LINQ or there are batter library?
Do you need to modify the collection in place or return a new collection?
If you are returning a new collection you could
var query = from x in oldItems
join y in newItems on y.Id equals x.Id into g
from z in g.DefaultIfEmpty()
select z ?? x;
var new List = query.ToList();
This method will ignore entries in newItems that do not exist in old items.
If you are going to be modifying the collection in place you would be better off working with a dictionary and referencing that everywhere.
You can create a dictionary from the list by doing
var collection = items.ToDictionary(x => x.Id, x => x);
Note modifying the dictionary doesn't alter the source collection, the idea is to replace your collection with the dictionary object.
If you are using the dictionary you can then iterate over new collection and check the key.
foreach (var item in newItems.Where(x => collection.ContainsKey(x.Id))) {
collection[item.Id] = item;
}
Dictionaries are iterable so you can loop over the Values collection if you need to. Adds and removes are fast because you can reference by key. The only problem I can think you may run into is if you rely on the ordering of the collection.
If you are stuck needing to use the original collection type then you could use the ToDictionary message on your newItems collection. This makes your update code look like this.
var converted = newItems.ToDictionary(x => x.Id, x => x);
for (var i = 0; i < oldItems.Count(); i++) {
if (converted.ContainsKey(oldItems[i].Id)) {
oldItems[i] = converted[oldItems[i].Id];
}
}
This has the advantage the you only need to loop the newitems collection once, from then on it's key lookups, so it's less cpu intensive. The downside is you've created an new collection of keys for newitems so it consumes more memory.
Send you a sample function that joins the two list by id property of both lists and then update original Product.url with the newer one
void ChangeItems(IList<Product> original, IList<Product> newer){
original.Join(newer, o => o.id, n => n.id, (o, n) => new { original = o, newer = n })
.ToList()
.ForEach(j => j.original.Url = j.newer.Url);
}
Solution :- : The LINQ solution you're look for will be something like this
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? newList.FirstOrDefault(newObj => newObj.id == ele.id) : ele); }).ToList();
Note :- Here we are creating the OldList based on NewList & OldList i.e we are replacing OldList object with NewList object.If you only want some of the new List properties you can create a copy Method in your class
EG for copy constructor
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? ele.Copy(newList.FirstOrDefault(newObj => newObj.id == ele.id)) : ele); }).ToList();
//Changes in your class
public void Copy(Product prod)
{
//use req. property of prod. to be replaced the old class
this.id = prod.id;
}
Read
It is not a good idea to iterate over 10k+ elements even using linq as such it will still affect your CPU performance*
Online sample for 1st solution
As you have class
public class Product
{
public int id;
public string url;
public string otherData;
public Product(int id, string url, string otherData)
{
this.id = id;
this.url = url;
this.otherData = otherData;
}
public Product ChangeProp(Product newProd)
{
this.url = newProd.url;
this.otherData = newProd.otherData;
return this;
}
}
Note that, now we have ChangeProp method in data class, this method will accept new class and modify old class with properties of new class and return modified new class (as you want your old class be replaced with new classes property (data). So at the end Linq will be readable and clean.
and you already have oldList with lots of entries, and have to replace data of oldList by data of newList if id is same, you can do it like below.
suppose they are having data like below,
List<Product> oldList = new List<Product>();
for (int i = 0; i < 10000; i++)
{
oldList.Add(new Product(i, "OldData" + i.ToString(), "OldData" + i.ToString() + "-other"));
}
List<Product> newList = new List<Product>();
for (int i = 0; i < 5; i++)
{
newList.Add(new Product(i, "NewData" + i.ToString(), "NewData" + i.ToString() + "-other"));
}
this Linq will do your work.
oldList.Where(x => newList.Any(y => y.id == x.id))
.Select(z => oldList[oldList.IndexOf(z)].ChangeProp(newList.Where(a => a.id == z.id).FirstOrDefault())).ToList();
foreach(var product in newList)
{
int index = oldList.FindIndex(x => x.id == product.id);
if (index != -1)
{
oldList[index].url = product.url;
}
}
This will work and i think it's a better solution too.
All the above solution are creating new object in memory and creating new list with 10k+
records is definitely a bad idea.
Please make fields in product as it won't be accessible.

Linq to get difference of two listviews and put it in third in Windows Form C#

I have two list views which have same data but differing in the number of records. I want to get the non-matching listviewitems in third list view. I have using the following code but it is not helping. The variables x and y are making problem.
var list1Source = lvFace.Items.Cast<ListViewItem>();
var list2Source = lvDBdata.Items.Cast<ListViewItem>();
lvDataToUpload = list1Source.Where(
(x => list2Source.All(y => y.Text != x.Text));
You are looking for LINQ Except method
var lvExcept1 = list1Source.Except(list2Source);
var lvExcept2 = list2Source.Except(list1Source);
lvDataToUpload = lvExcept1.Union(lvExcept2);
But you need to override Equals and GetHashCode methods for your ListViewItem class. If there is no option to do this (ListViewItem is Windows Forms class, not yours), you can define your own equality comparer:
public class ListViewItemComparer : IEqualityComparer<ListViewItem>
{
bool IEqualityComparer<ListViewItem>.Equals(ListViewItem x, ListViewItem y)
{
return (x.Text == y.Text);
}
int IEqualityComparer<ListViewItem>.GetHashCode(ListViewItem obj)
{
if (Object.ReferenceEquals(obj, null))
return 0;
return obj.Text.GetHashCode();
}
}
And final code is:
var lvExcept1 = list1Source.Except(list2Source, new ListViewItemComparer());
var lvExcept2 = list2Source.Except(list1Source, new ListViewItemComparer());
lvDataToUpload = lvExcept1.Union(lvExcept2);
LINQ doesn't have a "set difference" operator itself... but you can use Except twice:
var list1Text = list1Source.Select(x => x.Text);
var list2Text = list2Source.Select(x => x.Text);
var difference = list1Text.Except(list2Text)
.Concat(list2Text.Except(list1Text))
.ToList();
Try this
listIntersection = list1Source.Intersect(list2Source); // Gets matching elements
listUnion = list1Source.Union(list2Source); // Gets all elements
lvDataToUpload = listUnion.Except(listIntersection);

What is the best way to return a projected query to add filter afterward

I need to make a query that return all items with the current price and the current reduction if any.
I tried a few solutions but none seem to work or respect the patterns as i understand them.
The dynamic solution:
I tried to return the data as a dynamic that would be an IQueryable where T would be (Item, CurrentItemPrice, CurrentItemStateIfAny)
public ItemRepository(CoconutEntities context) : base(context){}
public dynamic GetAllCurrentItems(){
var items = (from item in context.Items
select new {
Item = item,
CurrentItemPrice = item.ItemPrices.Where(x => item.ItemPrices.Max(y => y.EffectiveDate) == x.EffectiveDate),
CurrentItemState = item.ItemReductions.Where(x => x.StartDate <= DateTime.Now && DateTime.Now <= x.EndDate)});
return items;
}
But when i try this and i need to add filter, i can't add them the way i was expecting.
public dynamic GetCurrentItems(string filter = "", int categoryId = 1) {
dynamic result;
var categoryServices = new CategoryServices();
IEnumerable<int> categoryIdAndChildCategoriesId = categoryServices.GetCategoryIdAndChildsId(categoryId);
if (!string.IsNullOrWhiteSpace(filter))
{
result = this.GetAllCurrentItems().Where(x => ((string)(x.Item.Name)) == filter);
}
else if(categoryId != 1)
{
result = this.GetAllCurrentItems().Where(x => x.Item.ItemCategories.Any(x => categoryIdAndChildCategoriesId.Contains(x.CategoryId)));
}
return result;
}
Solution 2 : I also tried with Tuple where i should have been able to do somthing like this but i can't create Tuples from Linq to Entities if i understood in an other post. I would need to query all the item first, then use linq to object to create my tuples.
Solution 3 : I can create a viewmodel or a new model that would represent the data i need. I know this would work but i don't understand where it would stand between the two. If it is not a view model, this information won't go to the view it an other way to see an item with only the current information.
In short, there are probably many solutions to this problem, but i need help to understand which solution would be the best and why.
As I understood you you want to do as much as possible on the database - that is good. You might achieve that with tuples like that:
public IEnumerable<Tuple<Item,decimal, decimal>> GetAllCurrentItems(Expression<Func<Item, bool>> filterExpression){
using(MyContext context = new MyContext())
{
var items = context.Items
.Where(filterExpression)
.Select(item => new Tuple<Item,decimal, decimal> (
item,
item.ItemPrices.Where(x => item.ItemPrices.Max(y => y.EffectiveDate) == x.EffectiveDate),
item.ItemReductions.Where(x => x.StartDate <= DateTime.Now && DateTime.Now <= x.EndDate)});
return items;
}
}
And calling it like that:
public IEnumerable<Tuple<Item,decimal, decimal>> GetCurrentItems(string filter = "", int categoryId = 1) {
dynamic result;
var categoryServices = new CategoryServices();
IEnumerable<int> categoryIdAndChildCategoriesId = categoryServices.GetCategoryIdAndChildsId(categoryId);
if (!string.IsNullOrWhiteSpace(filter))
{
result = this.GetAllCurrentItems(x => ((string)(x.Item.Name)) == filter);
}
else if(categoryId != 1)
{
result = this.GetAllCurrentItems(x => x.Item.ItemCategories.Any(x => categoryIdAndChildCategoriesId.Contains(x.CategoryId)));
}
return result;
}

Categories