Linq value, use Where to Remove items - c#

I am trying to remove items from an IQueryable list but the result is only pulling in those items:
public IQueryable<Biz.Data.AllItems> items_GetData()
{
var submissions = Biz.Data.AllItems.LoadNotDeleted().Where(x =>
// these items need to match to remove the item
x.itemOne != null &&
x.itemTwo != null &&
x.itemThree != null));
var filter = new Biz.Data.AllItemsFilter();
return submissions = Biz.Data.Registration.Load(filter).OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
}
Currently, it's only pulling in items that match those instead of removing. I can't use RemoveAll because it's not a List and I don't want to reformat this because it passes through a filter process after this code. Is there another way to remove items that match these results first before it passes through a filter?

As discussed in comments, simply negate the condition in your predicate.
So if this is your original statement:
var itemsThatMatch = list.Where(x => /* some condition */);
This will give you the opposite:
var itemsThatDoNotMatch = list.Where(x => !(/* some condition */));

Related

Filter a collection column by multiple values

I have a collection and I would like to filter it with one of the column contains multiple values. The filter values are dynamically generated and I dont know how many I will get.
I tried the following without success:
var input = #"was.Name.Contains(""Test"") || was.Name.Contains(""Test2"")";
var test = collection.Where(was => input)).ToList();
Assuming you receive the filter values as a CSV string:
var csvFilters = "Test1, Test2";
// split by ',', remove empty entries,
// trim each filter and store the result in a list
var filters = csvFilters.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
// return items in collection whose Name property
// is equal to any of the items in filters
var result = collection.Where(x => filters.Contains(x.Name)).ToList();
This should translate to the following SQL:
SELECT * FROM collection c
WHERE c.Name IN ('Test1', 'Test2')
I guess you want to use LINQ. The question is, how the "filter" values are kept? I'll answer in the way I understand your question.
If input is supposed to be a condition then I'd suggest using Func<Object,bool>. This means, the input would be the condition you're looking for, and if found, it would return true.
Here is a simple example:
IEnumerable <T> FindElements (Func<Object, bool> condition, IEnumerable<T> inputList)
{
List<T> outputList = new List<T>();
foreach(var element in inputList)
{
if(condition != null && condition(element))
outputList.Add(element);
}
return outputList;
}
Then, if you call the function given exemplary parameters:
string input[] = {"Test1","Test2"};
foreach(string s in input)
{
targetList = FindElements(element=>((cast)element).Name.Contains(s), collection);
}
You should get all elements in collection which name has Test1 or Test2. Cast is of course name of the class which element instantiates.

C# How to split a List in two using LINQ [duplicate]

This question already has answers here:
Can I split an IEnumerable into two by a boolean criteria without two queries?
(6 answers)
Closed 2 years ago.
I am trying to split a List into two Lists using LINQ without iterating the 'master' list twice. One List should contain the elements for which the LINQ condition is true, and the other should contain all the other elements. Is this at all possible?
Right now I just use two LINQ queries, thus iterating the (huge) master List twice.
Here's the (pseudo) code I am using right now:
List<EventModel> events = GetAllEvents();
List<EventModel> openEvents = events.Where(e => e.Closer_User_ID == null);
List<EventModel> closedEvents = events.Where(e => e.Closer_User_ID != null);
Is it possible to yield the same results without iterating the original List twice?
You can use ToLookup extension method as follows:
List<Foo> items = new List<Foo> { new Foo { Name="A",Condition=true},new Foo { Name = "B", Condition = true },new Foo { Name = "C", Condition = false } };
var lookupItems = items.ToLookup(item => item.Condition);
var lstTrueItems = lookupItems[true];
var lstFalseItems = lookupItems[false];
You can do this in one statement by converting it into a Lookup table:
var splitTables = events.Tolookup(event => event.Closer_User_ID == null);
This will return a sequence of two elements, where every element is an IGrouping<bool, EventModel>. The Key says whether the sequence is the sequence with null Closer_User_Id, or not.
However this looks rather mystical. My advice would be to extend LINQ with a new function.
This function takes a sequence of any kind, and a predicate that divides the sequence into two groups: the group that matches the predicate and the group that doesn't match the predicate.
This way you can use the function to divide all kinds of IEnumerable sequences into two sequences.
See Extension methods demystified
public static IEnumerable<IGrouping<bool, TSource>> Split<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate)
{
return source.ToLookup(predicate);
}
Usage:
IEnumerable<Person> persons = ...
// divide the persons into adults and non-adults:
var result = persons.Split(person => person.IsAdult);
Result has two elements: the one with Key true has all Adults.
Although usage has now become easier to read, you still have the problem that the complete sequence is processed, while in fact you might only want to use a few of the resulting items
Let's return an IEnumerable<KeyValuePair<bool, TSource>>, where the Boolean value indicates whether the item matches or doesn't match:
public static IEnumerable<KeyValuePair<bool, TSource>> Audit<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate)
{
foreach (var sourceItem in source)
{
yield return new KeyValuePair<bool, TSource>(predicate(sourceItem, sourceItem));
}
}
Now you get a sequence, where every element says whether it matches or not. If you only need a few of them, the rest of the sequence is not processed:
IEnumerable<EventModel> eventModels = ...
EventModel firstOpenEvent = eventModels.Audit(event => event.Closer_User_ID == null)
.Where(splitEvent => splitEvent.Key)
.FirstOrDefault();
The where says that you only want those Audited items that passed auditing (key is true).
Because you only need the first element, the rest of the sequence is not audited anymore
GroupBy and Single should accomplish what you're looking for:
var groups = events.GroupBy(e => e.Closer_User_ID == null).ToList(); // As others mentioned this needs to be materialized to prevent `events` from being iterated twice.
var openEvents = groups.SingleOrDefault(grp => grp.Key == true)?.ToList() ?? new List<EventModel>();
var closedEvents = groups.SingleOrDefault(grp => grp.Key == false)?.ToList() ?? new List<EventModel>();
One line solution by using ForEach method of List:
List<EventModel> events = GetAllEvents();
List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();
events.ForEach(x => (x.Closer_User_ID == null ? openEvents : closedEvents).Add(x));
You can do without LINQ. Switch to conventional loop approach.
List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();
foreach(var e in events)
{
if(e.Closer_User_ID == null)
{
openEvents.Add(e);
}
else
{
closedEvents.Add(e);
}
}

How to get next item from list based on some condition in C#?

I have a List which contains set of time frames (DataTime format). It has StartDateTime & EndDateTime. I am trying to get a next item of the list based on a condition. How can I do that?
For Example,
foreach (var currentTimeSlot in prepBlock.EligiblePickupTimes.BlockList)
{
if (potentialStartTime > currentTimeSlot.EndDateTime)
{
//Skip current time slot and grab next one and so on.
}
}
You can use FirstOrDefault in order to get the first item matching your predicate:
prepBlock.EligiblePickupTimes.BlockList
.FirstOrDefault(x => potentialStartTime <= x.EndDateTime);
You can get the entire Enumerable<T> of items from the first matches this condition to the end using SkipWhile:
prepBlock.EligiblePickupTimes.BlockList
.SkipWhile(x => potentialStartTime > x.EndDateTime);
The first condition is equivalent to the following code:
prepBlock.EligiblePickupTimes.BlockList
.SkipWhile(x => potentialStartTime > x.EndDateTime)
.FirstOrDefault();
From what is see you try to do in the image you can do the following:
returnValue.IsEstimateSuccessful &= !prepBlock.EligiblePickupTimes.BlockList
.SkipWhile(x => potentialStartTime > x.EndDateTime)
.Any();
Unless I'm missing something, I believe you can accomplish this with the .FirstOrDefault method as mentioned in comments.
using System.Linq;
...
var nextAvailableItem =
prepBlock.EligiblePickupTimes.BlockList
// reversing your condition above to find value I want
// instead of specifying values I don't want
.FirstOrDefault(x => potentialStartTime <= x.EndDateTime)
;
// did we find a value to match our condition?
var wasFound = nextAvailableItem != default(DateTime);
If you are just trying to loop through all the timeslots where potentialStartTime is greater than it's EndDateTime, then:
foreach (var currentTimeSlot in
prepBlock.EligiblePickupTimes.BlockList.Where(x=>potentialStartTime > x.EndDateTime))
{
}
based on your image, I think this is what you are looking for however:
returnValue.IsEstimateSuccessful=!prepBlock
.EligiblePickupTimes
.BlockList
.Any(x=>potentialStartTime > x.EndDateTime);
if returnValue.IsEstimateSuccessful is set prior to that (like if you default it to true, and many checks might turn it false):
returnValue.IsEstimateSuccessful&=!prepBlock
.EligiblePickupTimes
.BlockList
.Any(x=>potentialStartTime > x.EndDateTime);

Can I add a where clause to an IOrderedEnumerable list?

I have a list of arrays. Each array consists of a score and a difficulty. Read in from a text file.
This is how I get the data and order it by the score in descending order.
// load highscores
public void LoadScores()
{
// loop through each line in the file
while ((line = file.ReadLine()) != null)
{
// seperate the score from the difficulty
lineData = line.Split(',');
// add each line to the list
list.Add(lineData);
}
// order the list by descending order
IOrderedEnumerable<String[]> scoresDesc = list.OrderByDescending(ld => lineData[0]);
}
Is it possible to add the a clause to the IOrderedEnumerable so that it orders by the score in descending order where the difficulty is 1 ?
Assuming the "difficulty" is the second item in the array:
IEnumerable<String[]> scoresDesc =
list.OrderByDescending(ld => lineData[0])
.Where(ld => lineData[1] == 1);
You can sort it afterwards, but Where returns an IEnumerable<T>, not an IOrderedEnumerable<T>, so if you need it to be an IOrderedEnumerable<T> then it's cleaner (and faster) to filter the list first:
IOrderedEnumerable<String[]> scoresDesc =
list.Where(ld => lineData[1] == 1)
.OrderByDescending(ld => lineData[0]);
(this is where var eases some pain, since you aren't bound to the return type)
If you want to filter for those of difficulty 1, you can use the .Where() extension method, if you want to sort by more than one field, you can use the .ThenBy() extension method.
first filter then order:
list.Where(x => x.difficulty == 1).OrderByDescending(ld => lineData[0]);

LINQ for removing elements that are started with other element from list

I have a list List<string> with some paths.
C:\Dir\Test\
C:\MyDir\
C:\YourDir\
C:\Dir\
I want to go through all the elements (using LINQ) and remove entries that are started with other element from my list.
In my example C:\Dir\Test\ starts with C:\Dir\ - so I want to remove C:\Dir\Test\.
Use List<T>.RemoveAll() method:
sourceList.RemoveAll(x => sourceList.Any(y => x != y && x.StartsWith(y)));
Try this:
myInitialList.RemoveAll(x =>myInitialList.Any(q => q != x && q.StartsWith(x)));
Or if you want to keep the original list, this is a way to get all the records that do not match your criteria:
List<string> resultList = myInitialList.Except(x => myInitialList.Any(q => q != x && q.StartsWith(x)));
How about
mylist = mylist.Where(a => mylist.All(b => b == a || !a.StartsWith(b)))
.Distinct()
.ToList();
This will return a new list where there isn't another item in the list that it starts with.
It has the extra check to allow returning the value where there string is the same, otherwise all items would be removed from the list.
Finally the distinct call means that two occurrences of the same string are removed.
Building on nsinreal's comment and solution you could do something like
myList = myList.OrderBy(d => d)
.Aggregate(new List<string>(),
(list, item) => {
if (!list.Any(x => item.StartsWith(x)))
list.Add(item);
return list;
}).ToList();
This reduces the complexity of the solution by reducing the size of the search list for each test. It still requires an initial sort.
Personally I find this alternative solution harder to read and my first answer is more expressive the problem to solve.
The most efficient way is IMO to sort the paths, then iterate them and return only the ones not starting as one of the previous, i.e. :
public static IEnumerable<string>
GetRootPathsOfSet(this IEnumerable<string> paths)
{
var sortedSet = new SortedSet<string>(paths,
StringComparer.CurrentCultureIgnoreCase);
string currRoot = null;
foreach (var p in sortedSet)
{
if (currRoot == null ||
!p.StartsWith(currRoot, StringComparison.InvariantCultureIgnoreCase))
{
currRoot = p;
yield return currRoot;
}
}
}
Some notes:
All the paths MUST terminate with a trailing back-slash, otherwise the StartsWith approach is not safe (e.g. C:\Dir and C:\Directory)
This code uses case-insensitive comparison
I'm not using pure LINQ here, but it's an extension method

Categories