I'd like to use the Uber-Coolness of LINQ set operations to express the following :
foreach (Group group in groups)
{
if (user.Groups.Contains(group))
{
assignedGroups.Add(group);
}
else
{
availableGroups.Add(group);
}
}
I thought it should be a two-liner achieving this :
var assigned = user.Groups.Intersect(groups);
var available = groups.Except(user.Groups);
Whenever I run this example the foreach approach fills my lists correctly, while the set operations result in an empty assigned list and a filled available list.
I thought it must be a problem concerning the equality check, but the fact that Contains() is working proves this wrong.
Can anyone help me see my misconception here?
the IEnumerable groups is also result of a LINQ query, just in case that information is of some help...
Well, it shouldn't make a difference, but from the point of view of symmetry I'd reverse how you're creating assigned. I'd also make sure that the query is only executed once, and that the remaining operations occur in-process:
var cachedGroups = groups.ToList();
var assigned = cachedGroups.Intersect(user.Groups);
var available = cachedGroups.Except(user.Groups);
One possibility is that user.Groups has a custom equality comparer. That would explain why the foreach version worked but the LINQ version didn't. What's the type of user.Groups, and how much do you know about the equality comparer it's using?
Related
Lets say you want to compare two lists of objects based on a truncated (I think this is the word) version of a DateTime property:
public class MyObject
{
public DateTime SomeDate {get;set;}
}
Compare lists normally:
bool didItWork = myFirstList.Intersect(MySecondList).Any();
All fine and good here except I need to do this check based on the above DateTime property:
bool didItWork = myFirstLIst.Intersect(MySecondList).Any(someIntuitivePredicate);
As you can see I don't know what the intuitive predicate is.
And for a kicker I want to cut the Time section off of SomeDate when I do the compare:
dateWithoutTime = myObject.SomeDate.Date;
The time is not important just the actual date.
Seeing you unmarked the answer, I'm going to take a crack at this, however the answers really are good efforts, but a result of your vague problem definition. Even your comment "If there are any items in either list that do not exist in the other" is a little awkwardly worded. Not trying to grill you, but just keep that in mind. When dealing with set operations you have to take a step back and really think carefully about what you want to accomplish. I'm going to interpret your goal as this, and if this is incorrect, you should update your question with a more clearly defined problem definition:
I want to check and make sure that for every item in myFirstList,
there is an item in mySecondList with the same SomeDate.Date, and vice
versa.
If you are truncating time from a date, then I would speculate there might be cases where there are two items with the same date. If this were not the case, there are easier methods that take approaches such as joining or intersecting the lists and checking that the results have equal Count as the originals(which verifies that all items found a match). The other answers using join or intersect don't quite work, because they use .Any() which will return true if just one of the items matches, rather than all.
.All will make sure that all items in one list meet some criteria. In this case, make sure every item has a match in the second list.
bool isListEqual = myFirstList.All(x=>
mySecondList.Select(y=>y.SomeDate.Date).Contains( x.SomeDate.Date) )
isListEqual = isListEqual && mySecondList.All(x=>
myFirstList.Select(y=>y.SomeDate.Date).Contains( x.SomeDate.Date) )
We do it both directions to ensure there are no items in the second list without a matching item in the first.
If we knew there were no duplicates, another approach would be to join the lists and count the results to ensure they match the originals. Or we could simply eliminate duplicates to generate distinct lists. You can also use SetEquals of hashset, or SequenceEquals of IEnumerable. SequenceEquals is better if you know your results are already in a particular order.
var firstDates = myFirstList.Select(l=>l.SomeDate.Date).Distinct().OrderBy(d=>d).ToList();
var secondDates = mySecondList.Select(l=>l.SomeDate.Date).Distinct().OrderBy(d=>d).ToList();
bool isListEqual = firstDates.SequenceEqual(secondDates);
I think the most important thing you should take from this, is the approach is dependent on what assumptions you can make about your input data.
This question covers several approaches, and the duplicate it is linked to as well:
Check if two lists are equal
As far as I can see, you have two options:
Implement a custom IEqualityComparer and pass it to the Intersect overload that takes an IEqualityComparer.
Or
Simulate Intersect with nested Any:
bool didItWork = myFirstList.Any(item1 =>
mySecondList.Any(item2 =>
item1.SomeDate.Date == item2.SomeDate.Date));
Obviously, item1.SomeDateDate == item2.SomeDate.Date can be replaced by an arbitrary predicate.
Here's an alternative version for those who prefer the LINQ query syntax:
bool didItWork = (from item1 in myFirstList
from item2 in mySecondList
where item1.SomeDate.Date == item2.SomeDate.Date).Any();
You can use an overload of the Intersect method that also takes an IEqualityComparer implementation as an input:
bool didItWork = myFirstList.Intersect(MySecondList, new MyEqualityComparer()).Any();
// ...
public class MyEqualityComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
return x.SomeDate.Date == y.SomeDate.Date;
}
public int GetHashCode(MyObject x)
{
return x.SomeDate.Date.GetHashCode();
}
}
I have two lists. The first is of all students and the second is of selected students. I want if I one time select some student they will remove from the all-student list. Here is my code but it doesn't. Students won't get removed.
foreach (var li in ListSelectedStudents.ToList())
{
if (ListAllStudents.Contains(li))
{
ListAllStudents.Remove(li);
}
}
Contains will use equality to determine what is "equal", I am assuming here that your custom class hasn't provided a custom equality implementation, which means the default equatable will be provided for that type and it's just using reference equality. So even though you think two things are "equal", the Contains method doesn't and so doesn't step into the Remove call.
To get that particular code to behave what you need to do is provide an implementation of IEquatable<Student> on the Student class, as described in the remarks here.
In this instance, Contains isn't actually required as Remove will do the same checks. If there is nothing to remove, the Remove call will be transparent, effectively doing nothing.
As has been caught in the comments before I had chance to provide the information, Remove will also rely on IEquatable<Student> (docs) so you still need to provide an implementation, but it will make your code look a little cleaner:
foreach (var li in ListSelectedStudents.ToList())
{
ListAllStudents.Remove(li);
}
There may be various ways to do this without the need to implement the interface, but you won't be able to use your current code for it. I'll leave other answers to field those alternatives as it's Friday and my brain is not yet functioning properly.
have you tried using linq:
ListAllStudents.RemoveAll(m => ListSelectedStudents.Contains(m));
if it does not work, it could be something wrong with the default comparison implemented in the object, and you could either fix the comparer, or do something like:
ListAllStudents.RemoveAll(m => ListSelectedStudents.Any(n=>n.Id == m.Id)); // Assume the Id is the primary key of the object...
Try this:
ListSelectedStudents = ListSelectedStudents.Where(a => !ListSelectedStudents.Contains(a)).Select(a => a).ToList();
I'm new to LINQ and trying to get a hold of it.
It's been useful so far for various things such as cutting down the code required, like when using .ForEach() to run a function on every object.
Now I'm trying to get a list of all objects from a master list, when their IsMouseOver() function returns true.
As a standard foreach it looks like this:
this.m_EntHovered.Clear();
foreach (EntEditor ent in this.m_EntAll)
{
if (ent.IsMouseOver(mousePos))
this.m_EntHovered.Add(ent);
}
But I wanted to shortern this using LINQ, however the shortest I could get it wasn't much shorter:
this.m_EntHovered = (from ent in this.m_EntAll
where ent.IsMouseOver(input)
select ent).ToList<EntEditor>();
Is there a better way to achieve what I'm after or is what I'm doing fine?
There isn't necessarily a better way to do it, but you can write it more succinctly via:
this.m_EntHovered = this.m_EntAll.Where(ent => ent.IsMouseOver(input)).ToList();
Note that this is not the same as your original, however, as you're assigning a new list, instead of adding items to the existing list. To get the same behavior (which may not be needed), you could do:
this.m_EntHovered.Clear();
this.m_EntHovered.AddRange(this.m_EntAll.Where(ent => ent.IsMouseOver(input)));
How do I remove an object directly from an IGrouping IGrouping<DateTime, VMAppointment>?
The only way I know of currently is to generate a new IGrouping without the concering element, but I don't like this way because it causes some trouble within my application.
Any ideas?
No, there's no way to mutate an IGrouping<,>, at least in general - and even if you knew the concrete type, I don't believe any of the implementations exposed by the .NET framework allow the group to be mutated.
Presumably the grouping is the result of some query - so if possible, change the original query to exclude the values you aren't interested in.
I know this is old question, but hopefully this helps someone else. A workaround for this is to cast the group to a list, then use the values from the list instead of the group.
var groups = someList.GroupBy(x => x...);
foreach (var group in groups)
{
var groupList = group.ToList();
...
groupList.Remove(someItem);
//Process the other code from groupList.
}
You could cast using Select and use TakeWhile if you have a testable condition (such as null as in the example) on a property in your group:
var removedItemsList = group.Select(x => x.TakeWhile(t => t.someProperty != null));
This will return an IEnumerable<IEnumerable<YourGroup>>.
Ok, understand that I come from Cold Fusion so I tend to think of things in a CF sort of way, and C# and CF are as different as can be in general approach.
So the problem is: I want to pull a "table" (thats how I think of it) of data from a SQL database via LINQ and then I want to do some computations on it in memory. This "table" contains 6 or 7 values of a couple different types.
Right now, my solution is that I do the LINQ query using a Generic List of a custom Type. So my example is the RelevanceTable. I pull some data out that I want to do some evaluation of the data, which first start with .Contains. It appears that .Contains wants to act on the whole list or nothing. So I can use it if I have List<string>, but if I have List<ReferenceTableEntry> where ReferenceTableEntry is my custom type, I would need to override the IEquatable and tell the compiler what exactly "Equals" means.
While this doesn't seem unreasonable, it does seem like a long way to go for a simple problem so I have this sneaking suspicion that my approach is flawed from the get go.
If I want to use LINQ and .Contains, is overriding the Interface the only way? It seems like if there way just a way to say which field to operate on. Is there another collection type besides LIST that maybe has this ability. I have started using List a lot for this and while I have looked and looked, a see some other but not necessarily superior approaches.
I'm not looking for some fine point of performance or compactness or readability, just wondering if I am using a Phillips head screwdriver in a Hex screw. If my approach is a "decent" one, but not the best of course I'd like to know a better, but just knowing that its in the ballpark would give me little "Yeah! I'm not stupid!" and I would finish at least what I am doing completely before switch to another method.
Hope I explained that well enough. Thanks for you help.
What exactly is it you want to do with the table? It isn't clear. However, the standard LINQ (-to-Objects) methods will be available on any typed collection (including List<T>), allowing any range of Where, First, Any, All, etc.
So: what is you are trying to do? If you had the table, what value(s) do you want?
As a guess (based on the Contains stuff) - do you just want:
bool x= table.Any(x=>x.Foo == foo); // or someObj.Foo
?
There are overloads for some of the methods in the List class that takes a delegate (optionally in the form of a lambda expression), that you can use to specify what field to look for.
For example, to look for the item where the Id property is 42:
ReferenceTableEntry found = theList.Find(r => r.Id == 42);
The found variable will have a reference to the first item that matches, or null if no item matched.
There are also some LINQ extensions that takes a delegate or an expression. This will do the same as the Find method:
ReferenceTableEntry found = theList.FirstOrDefault(r => r.Id == 42);
Ok, so if I'm reading this correctly you want to use the contains method. When using this with collections of objects (such as ReferenceTableEntry) you need to be careful because what you're saying is you're checking to see if the collection contains an object that IS the same as the object you're comparing against.
If you use the .Find() or .FindAll() method you can specify the criteria that you want to match on using an anonymous method.
So for example if you want to find all ReferenceTableEntry records in your list that have an Id greater than 1 you could do something like this
List<ReferenceTableEntry> listToSearch = //populate list here
var matches = listToSearch.FindAll(x => x.Id > 1);
matches will be a list of ReferenceTableEntry records that have an ID greater than 1.
having said all that, it's not completely clear that this is what you're trying to do.
Here is the LINQ query involved that creates the object I am talking about, and the problem line is:
.Where (searchWord => queryTerms.Contains(searchWord.Word))
List<queryTerm> queryTerms = MakeQueryTermList();
public static List<RelevanceTableEntry> CreateRelevanceTable(List<queryTerm> queryTerms)
{
SearchDataContext myContext = new SearchDataContext();
var productRelevance = (from pwords in myContext.SearchWordOccuranceProducts
where (myContext.SearchUniqueWords
.Where (searchWord => queryTerms.Contains(searchWord.Word))
.Select (searchWord => searchWord.Id)).Contains(pwords.WordId)
orderby pwords.WordId
select new {pwords.WordId, pwords.Weight, pwords.Position, pwords.ProductId});
}
This query returns a list of WordId's that match the submitted search string (when it was List and it was just the word, that works fine, because as an answerer mentioned before, they were the same type of objects). My custom type here is queryTerms, a List that contains WordId, ProductId, Position, and Weight. From there I go about calculating the relevance by doing various operations on the created object. Sum "Weight" by product, use position matches to bump up Weights, etc. My point for keeping this separate was that the rules for doing those operations will change, but the basic factors involved will not. I would have even rather it be MORE separate (I'm still learning, I don't want to get fancy) but the rules for local and interpreted LINQ queries seems to trip me up when I do.
Since CF has supported queries of queries forever, that's how I tend to lean. Pull the data you need from the db, then do your operations (which includes queries with Aggregate functions) on the in-memory table.
I hope that makes it more clear.