order the list based on a condition - c#

I want to order a lambda result . I did this:
var dicWords= _context.dicWords
.Select(p=>new WordsDicDto
{
Id=p.Id,
OrderingLetter=p.OrderingLetter,
Word=p.Word,
})
.ToList();
I want to order the list by this condition. if the OrderingLetter is numeric it should be ordered by int.parse(p.OrderingLetter) and if it is not numeric so it should ordered by p,OrderingLetter itself. how should accomplish that?

If I understand your question right, you are looking for something like shown below. The most important place is the CompareTo() method which defines which order two elements have to each other. A negative value means that the current instance (accessed by this.) is preceding obj in sort order. Positive means the opposite. (See also here for the official documentation for CompareTo(): https://learn.microsoft.com/en-us/dotnet/api/system.icomparable.compareto?view=net-6.0#returns)
var dicWords = new List<WordsDicDto>(){
new WordsDicDto() {Id = "1", OrderingLetter = "M", Word = "Mouse" },
new WordsDicDto() {Id = "3", OrderingLetter = "2", Word = "Bycicle"},
null,
new WordsDicDto() {Id = "4", OrderingLetter = "1", Word = "Dog"},
new WordsDicDto() {Id = "2", OrderingLetter = "C", Word = "Car"},
};
Console.WriteLine("The words before sorting:");
dicWords.ForEach(p => Console.WriteLine($"{p?.Id} | {p?.OrderingLetter} | {p?.Word}"));
// This is the filtering
var result = dicWords.OrderBy<WordsDicDto, object>(p => p).ToList();
Console.WriteLine("--");
Console.WriteLine("The words after sorting:");
result.ForEach(p => Console.WriteLine($"{p?.Id} | {p?.OrderingLetter} | {p?.Word}"));
This is the used implementation of WordsDicDto with the implementation of CompareTo():
internal class WordsDicDto : IComparable
{
public string Id { get; set; }
public string OrderingLetter { get; set;}
public string Word { get; set; }
public int CompareTo(object obj)
{
if (obj is WordsDicDto p)
{
if (int.TryParse(this.OrderingLetter, out int instanceValue) &&
int.TryParse(p.OrderingLetter, out int numericValue))
{
return instanceValue - numericValue;
}
else
{
return String.Compare(this.OrderingLetter, p.OrderingLetter, StringComparison.Ordinal);
}
}
else
{
return -1;
}
}
}

Related

How to remove all next elements from list of object on condition in c#

I have one list of objects
public class Card
{
public int Id { get; set; }
public double Price { get; set; }
public string Name{ get; set; }
}
How to remove all next elements from list when there is a match on condition something like-
if(object1.Price== 0)
{
//remove all next elements from list
}
I want to keep the list from 0 to matching position like if price is zero at index 5 then take 0 to 5 elements.
You can use a combination of TakeWhile and Select, e.g.:
var lst = new List<Card>() {
new Card() { Price = 2 },
new Card() { Price = 1 },
new Card() { Price = 0 },
new Card() { Price = -1 },
new Card() { Price = -2 },
};
var found = false;
var items = lst.TakeWhile(x => !found).Select(x =>
{
if (x.Price == 0)
found = true;
return x;
});
This leads to items with price 2, 1, 0 being kept while -1 and -2 are removed.
See this fiddle to test.
If you are using a List<T> where T is Card you can use List<T>.FindIndex to get the index of the first element matching the predicate and get the range of the List:
List<Card> cards = new List<Card> { new Card {Id = 1, Price = 1.0, Name = "Ace" },
new Card {Id = 2, Price = 0.0, Name="Two" }};
Predicate<Card> test1 = a => a.Price == 1.0;
Predicate<Card> test2 = b => b.Price == 0.0;
//You can also do something like this:
Predicate<Card> test3 = c => c.Price == 1.0 && c.Id == 1;
Console.WriteLine(GetCardRange(cards, test1).Count); //Prints 1
Console.WriteLine(GetCardRange(cards, test2).Count); //Prints 2
Console.WriteLine(GetCardRange(cards, test3).Count); //Prints 1
//or shorthand without declaring a Predicate:
Console.WriteLine(GetCardRange(cards, (a) => a.Price == 0.0).Count); //Prints 2
//Method accepts a predicate so you can pass in different
//types of condition(s)
public List<Card> GetCardRange(List<Card> cards, Predicate<Card> p)
{
int index = cards.FindIndex(p);
//if no card is found, either return the whole list
//or change to return null or empty list
return index == -1 ? cards :
cards.GetRange(0, index + 1);
}
public class Card
{
public int Id { get; set; }
public double Price { get; set; }
public string Name{ get; set; }
}

Filter Collection matching fields with another List in memory

I have a list in memory. I am wishing to filter a collection's items matching with that list. The scenario is like the following:
var memoryList = new List<MyClass>()
{
new MyClass(){ Id = "1", Name ="aaa" },
new MyClass(){ Id = "2", Name ="bbb" },
new MyClass(){ Id = "3", Name ="ccc" }
};
IEnumerable<MyCollection> myDesiredItems = repository.GetItems<MyCollection>(c => memoryList.Any(m => m.Id == c.ItemId && m.Name == c.ItemName)).ToList();
public class MyClass
{
public string Id { get; set; }
public string Name { get; set; }
}
But I get Unsupported filter: Any(...) exception when I run my code. Any suggestion how can I achieve myDesiredItems?

Linq Groupby return original object

I need some help filtering some data. I've got an object class with three properties. The collection of objects I've got can have many matches of the first property, Point3d. From that collection of matches I need to see if the second property has unique values, Tag. Finally, I need to be able to identify the objects whos Point3d match, and Tags are different, using the third property, it's Id (which is always unique).
class pmatch
{
public string Point3d { get; set; }
public string Tag { get; set; }
public string Id { get; set; }
}
An example of what i'm looking for would be:
List<pmatch> dataset = new List<pmatch>
{
new pmatch { Point3d = "1, 1, 1", Tag = "5", Id = "123" },
new pmatch { Point3d = "1, 1, 1", Tag = "6", Id = "124" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "125" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "126" }
};
I need to be able to identify Id's 123 and 124, as their Point3ds match, but their Tags do not. I've been able to identify these instances using LINQ:
var result = datalist.GroupBy(item => item.Point3d, item => item.Tag);
foreach (var group in result)
{
Console.WriteLine(group.Key);
var uniqueTags = group.Distinct().ToList();
if (uniqueTags.Count > 1)
{
Console.WriteLine("Found mismatched tags");
foreach (string Tag in group)
{
Console.WriteLine(" {0}", Tag);
}
}
}
However these results do not give me the Id, so I can not access the object I have identified. How do I get these results along with the Id, or the pmatch object itself?
You can accomplish the desired result like so:
var resultSet =
dataset.GroupBy(item => item.Point3d)
.Where(group => group.Select(item => item.Tag)
.Distinct()
.Count() > 1)
.ToDictionary(item => item.Key, item => item.ToList());
This will identify Id's 123 and 124, as their Point3ds match, but their Tags do not and also resultSet is of type Dictionary<string, List<pmatch>> so you have access to all the properties of the pmatch object.

Excluding items from a list that exist in another list

I have a list for example List<string> ListProviderKeys that has some values in it.
I also have a second list from a class below, for example List<ChangesSummary> SecondList;
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName{ get; set; }
}
Imagine the values that first list holds is the same kind of values we put in ProviderKey field in the second list.
Now What I want is to trim down the second list to only have values that their ProviderKey IS NOT already in the first list.
How Can I do that? I know the operator Except but not sure how to apply it in this situation!
The best I can think of is :
A) Create dictionary and use its fast lookups
B) Use LINQ .Where method with .ContainsKey() on this dictionary which internally uses Hashtable and performs quick lookups.
This should reduce search complexity to almost O(1) rather than O(N) ro worse (when we use LINQ .Where() with .Any() or .Contains() and that leads to nested loops).
From MSDN page :
The Dictionary generic class provides a mapping from a set of keys to
a set of values. Each addition to the dictionary consists of a value
and its associated key. Retrieving a value by using its key is very
fast, close to O(1), because the Dictionary class is implemented as a
hash table.
So what we can do is :
Dictionary<string, string> dict = ListProviderKeys.ToDictionary(s => s);
var newList = SecondList.Where(e => !dict.ContainsKey(e.ProviderKey)).ToList();
Here is a very simple, short, but complete example illustrating it and also testing its performance :
class Person
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<int> ints = new List<int>();
List<Person> People = new List<Person>(1000);
for (int i = 0; i < 7500; i++)
{
ints.Add(i);
ints.Add(15000 - i - 1);
}
for (int i = 0; i < 45000; i++)
People.Add(new Person() { Id = i });
Stopwatch s = new Stopwatch();
s.Start();
// code A (feel free to uncomment it)
//Dictionary<int, int> dict = ints.ToDictionary(p => p);
//List<Person> newList = People.Where(p => !dict.ContainsKey(p.Id)).ToList();
// code B
List<Person> newList = People.Where(p => !ints.Contains(p.Id)).ToList();
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
Console.WriteLine("Number of elements " + newList.Count);
Console.ReadKey();
}
On release mode results are :
Both code A & code B outputs 30 000 elements but :
It took more than 2000 ms with code B and only 5 ms with code A
public class Programm
{
public static void Main()
{
List<ChangesSummary> summaries = new List<ChangesSummary>();
summaries.Add(new ChangesSummary()
{
FieldName = "1",
ProviderKey = "Test1",
});
summaries.Add(new ChangesSummary()
{
FieldName = "2",
ProviderKey = "Test2",
});
summaries.Add(new ChangesSummary()
{
FieldName = "3",
ProviderKey = "Test3",
});
List<string> listProviderKeys = new List<string>();
listProviderKeys.Add("Test1");
listProviderKeys.Add("Test3");
var res = summaries.Where(x => !listProviderKeys.Contains(x.ProviderKey));
res.ToList().ForEach(x => Console.WriteLine(x.ProviderKey));
Console.ReadLine();
}
}
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName { get; set; }
}
I think in this case simple Where would be easier and more readable to apply.
var first = new List<string> { "a" };
var second = new List<ChangesSummary>()
{
new ChangesSummary() { ProviderKey = "a" },
new ChangesSummary() { ProviderKey = "b" }
};
var result = second.Where(item => !first.Contains(item.ProviderKey));
// result
// .ToList()
// .ForEach(item => Console.WriteLine(item.ProviderKey));
I believe this will work:
List<ChangesSummary> ExceptionList = SecondList.
Where(x => !ListProviderKeys.Any(key => x.ProviderKey == key)).ToList();

Search in IQueryable<object>

Is it possible to search in an IQueryable
public static IQueryable<object> SearchAllFields(IQueryable<object> query, string term)
{
query = query.Where(q => q.Property1 == term);
query = query.Where(q => q.Property2 == term);
query = query.Where(q => q.Property3 == term);
return query;
}
Lets say I want to compare the search term to each of the properties that the object might have, without knowing before what properties the object might have.
Edit:
I am attempting to create a generic DataTable solution to display any tabular information that might be necessary (orders, books, customers, etc.)
To test the concept I'm using the ApplicationLogs table in my database. The DataTable looks as follows:
Lets say when typing in that search box I want to search for that value in all the columns that might be displayed. The query that populates the table:
IQueryable<object> query = (from log in db.ApplicationLog
orderby log.LogId descending
select new
{
LogId = log.LogId,
LogDate = log.LogDate.Value,
LogLevel = log.LogLevelId == 1 ? "Information" : log.LogLevelId == 2 ? "Warning" : "Error",
LogSource = log.LogSourceId == 1 ? "Www" : log.LogSourceId == 2 ? "Intranet" : "EmailNotification",
LogText = log.LogText
});
As you can see, this query will determine what the properties of the object will be. The example is taken from the logs table, but it can come from any number of tables. Then, if I want to call the generic search method from the original post:
query = DataTableHelper.SearchAllFields(query, pageRequest.Search);
You can use reflection to search through all the properties of the element, but if you want to return all rows where any property matches then you need to use predicatebuilder to build the applied query instead Where().
This example code will return both instances of Foo where A,B and C are "a". And the instances of Bar where E, F and G are "a". Also added example of anonymous type.
class Program
{
private class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
private class Bar
{
public string E { get; set; }
public string F { get; set; }
public string G { get; set; }
}
static void Main(string[] args)
{
var list = new List<Foo>
{
new Foo { A = "a", B = "a", C = "a" },
new Foo { A = "a2", B = "b2", C = "c2" },
new Foo { A = "a3", B = "b3", C = "c3" },
};
var list2 = new List<Bar>
{
new Bar { E = "a", F = "a", G = "a" },
new Bar { E = "a2", F = "b2", G = "c2" },
new Bar { E = "a3", F = "b3", G = "c3" },
};
var q1 = Filter(list.AsQueryable(), "a");
var q2 = Filter(list2.AsQueryable(), "a");
foreach (var x in q1)
{
Console.WriteLine(x);
}
foreach (var x in q2)
{
Console.WriteLine(x);
}
var queryable = list.Select(p => new
{
X = p.A,
Y = p.B,
Z = p.C
}).AsQueryable();
var q3 = Filter(queryable, "a");
foreach (var x in q3)
{
Console.WriteLine(x);
}
Console.ReadKey();
}
private static IQueryable<object> Filter(IQueryable<object> list, string value)
{
foreach (var prop in list.ElementType.GetProperties())
{
var prop1 = prop;
list = list.Where(l => Equals(prop1.GetValue(l, null), value));
}
return list;
}
}

Categories

Resources