(Disclaimer: I am new to LINQ, and I did do my homework, but all my research came up with examples that deal with strings directly. My code deals with an ObservableCollection of objects where each object contains a string.)
I have the following code:
public class Word
{
public string _word { get; set; }
// Other properties.
}
ObservableCollection<Word> items = new ObservableCollection<Word>();
items.Add(new Word() { _word = "Alfa" });
items.Add(new Word() { _word = "Bravo" });
items.Add(new Word() { _word = "Charlie" });
I am trying to find the words that have an "r" in them:
IEnumerable<Word> subset = from ii in items select ii;
subset = subset.Where(p => p._word.Contains("r"));
The above works (I get 'Bravo' and 'Charlie').
My question: I have devised the above LINQ query from bits and pieces of examples I found online/in books.
How does it do what it does?
Is there be a better/more straightforward way?
Thanks.
You could achieve that you want more simple like below:
IEnumerable<Word> subset = items.Where(x => x._word.Contains("r"));
With the above linq query, you filter the collection called items using an extension method called Where. Each element in the collection of items that satisfies the filter in the Where method, it will be contained on the subset. Inside the Where you have defined your fitler, using a lambda expression. On the left side of => you have your input -the random element of items, while on the right side of => you have your output, which in your case will be either true or false. On the rigth side of =>, you usethe string's method called Contains, which in simple terms checks if the string you pass as a parameter to this method is contained in the property called _word, of the element x.
Related
I can't quite get my head around this one for some reason.
Say we have a class Foo
public class Foo
{
public string Name {get;set;}
}
And we have a generic list of them. I want to search through the generic list and pick out those that have a Name that contains any from a list of strings.
So something like
var source = GetListOfFoos();//assume a collection of Foo objects
var keywords = GetListOfKeyWords();//assume list/array of strings
var temp = new List<Foo>();
foreach(var keyword in keywords)
{
temp.AddRange(source.Where(x => x.Name.Contains(keyword));
}
This issue here being a) the loop (doesn't feel optimal to me) and b) each object might appear more than once (if the name was 'Rob StackOverflow' and there was a keyword 'Rob' and keyword 'Stackoverflow').
I guess I could call Distinct() but again, it just doesn't feel optimal.
I think I'm approaching this incorrectly - what am I doing wrong?
I want to search through the generic list and pick out those that have
a Name that contains any from a list of strings.
Sounds rather easy:
var query = source.Where(e => keywords.Any(k => e.Name.Contains(k)));
Add ToList() to get results as a List<Foo>:
var temp = query.ToList();
Put the keywords into a HashSet for fast lookup, so that you're not doing a N2 loop.
HashSet<string> keywords = new HashSet<string>(GetListOfKeyWords(), StringComparer.InvariantCultureIgnoreCase);
var query = source.Where(x => keywords.Contains(x.Name));
EDIT: Actually, I re-read the question, and was wrong. This will only match the entire keyword, not see if the Name contains the keyword. Working on a better fix.
I like MarcinJuraszek's answer, but I would also assume you want case-insensitive matching of the keywords, so I'd try something like this:
var query = source.Where(f => keywords.Any(k => f.Name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0));
i got a generic list that looks like this:
List<PicInfo> pi = new List<PicInfo>();
PicInfo is a class that looks like this:
[ProtoContract]
public class PicInfo
{
[ProtoMember(1)]
public string fileName { get; set; }
[ProtoMember(2)]
public string completeFileName { get; set; }
[ProtoMember(3)]
public string filePath { get; set; }
[ProtoMember(4)]
public byte[] hashValue { get; set; }
public PicInfo() { }
}
what i'm trying to do is:
first, filter the list with duplicate file names and return the duplicate objects;
than, filter the returned list with duplicate hash value's;
i can only find examples on how to do this which return anonymous types. but i need it to be a generic list.
if someone can help me out, I'd appreciate it. also please explain your code. it's a learning process for me.
thanks in advance!
[EDIT]
the generic list contains a list of objects. these objects are pictures. every picture has a file name, hash value (and some more data which is irrelevant at this point). some pictures have the same name (duplicate file names). and i want to get a list of the duplicate file names from this generic list 'pi'.
But those pictures also have a hash value. from the file names that are identical, i want another list of those identical files names that also have identical hash values.
[/EDIT]
Something like this should work. Whether it is the best method I am not sure. It is not very efficient because for each element you are iterating through the list again to get the count.
List<PicInfo> pi = new List<PicInfo>();
IEnumerable<PicInfo> filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName)>1);
I hope the code isn't too complicated to need explaining. I always think its best to work it out on your own anyway but if anythign is confusing then just ask and I'll explain.
If you want the second filter to be filtering for the same filename and same hash being a duplicate then you just need to extend the lambda in the Count to check against hash too.
Obviously if you just want filenames at the end then it is easy enough to do a Select to get just an enumerable list of those filenames, possibly with a Distinct if you only want them to appear once.
NB. Code written by hand so do forgive typos. May not compile first time, etc. ;-)
Edit to explain code - spoilers! ;-)
In english what we want to do is the following:
for each item in the list we want to select it if and only if there is more than one item in the list with the same filename.
Breaking this down to iterate over the list and select things based on a criteria we use the Where method. The condition of our where method is
there is more than one item in the list with the same filename
for this we clearly need to count the list so we use pi.Count. However we have a condition that we are only counting if the filename matches so we pass in an expression to tell it only to count those things.
The expression will work on each item of the list and return true if we want to count it and false if we don't want to.
The filename we are interested in is on x, the item we are filtering. So we want to count how many items have a filename the same as x.FileName. Thus our expression is z=>z.FileName==x.FileName. So z is our variable in this expression and x.FileName in this context is unchanging as we iterate over z.
We then of course put our criteria in of >1 to get the boolean value we want.
If you wanted those that are duplicates when considering the filename and hashvalue then you would expand the part in the Count to be z=>z.FileName==x.FileName && z.hashValue==x.hashValue.
So your final code to get the distinct on both values would be:
List pi = new List();
List filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName && z.hashValue==x.hashValue)>1).ToList();
If you wanted those that are duplicates when considering the filename and hashvalue then you would expand the part in the Count to compare the hashValue as well. Since this is an array you will want to use the SequenceEqual method to compare them value by value.
So your final code to get the distinct on both values would be:
List<PicInfo> pi = new List<PicInfo>();
List<PicInfo> filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName && z.hashValue.SequenceEqual(x.hashValue))>1).ToList();
Note that I didn't create the intermediary list and just went straight from the original list. You could go from the intermediate list but the code would be much the same if going from the original as from a filtered list.
I think, you have to use SequenceEqual method for finding dublicate
(http://msdn.microsoft.com/ru-ru/library/bb348567.aspx).
For filter use
var p = pi.GroupBy(rs => rs.fileName) // group by name
.Where(rs => rs.Count() > 1) // find group whose count greater than 1
.Select(rs => rs.First()) // select 1st element from each group
.GroupBy(rs => rs.hashValue) // now group by hash value
.Where(rs => rs.Count() > 1) // find group has multiple values
.Select(rs => rs.First()) // select first element from group
.ToList<PicInfo>() // make the list of picInfo of result
I am learning to write lambda expressions, and I need help on how to remove all elements from a list which are not in another list.
var list = new List<int> {1, 2, 2, 4, 5};
var list2 = new List<int> { 4, 5 };
// Remove all list items not in List2
// new List Should contain {4,5}
// The lambda expression is the Predicate.
list.RemoveAll(item => item. /*solution expression here*/ );
// Display results.
foreach (int i in list)
{
Console.WriteLine(i);
}
You can do this via RemoveAll using Contains:
list.RemoveAll( item => !list2.Contains(item));
Alternatively, if you just want the intersection, using Enumerable.Intersect would be more efficient:
list = list.Intersect(list2).ToList();
The difference is, in the latter case, you will not get duplicate entries. For example, if list2 contained 2, in the first case, you'd get {2,2,4,5}, in the second, you'd get {2,4,5}.
Solution for objects (maybe easier than horaces solution):
If your list contains objects, rather than scalars, it is that simple, by removing by one selected property of the objects:
var a = allActivePatientContracts.RemoveAll(x => !allPatients.Select(y => y.Id).Contains(x.PatientId));
list = list.Except(list2).ToList();
This question has been marked as answered, but there is a catch. If your list contains an object, rather than a scalar, you need to do a bit more work.
I tried this over and over with Remove() and RemoveAt() and all sorts of things and none of them worked correctly. I couldn't even get a Contains() to work correctly. Never matched anything. I was stumped until I got the suspicion that maybe it could not match up the item correctly.
When I realized this, I refactored the item class to implement IEquatable, and then it started working.
Here is my solution:
class GenericLookupE : IEquatable<GenericLookupE>
{
public string ID { get; set; }
public bool Equals( GenericLookupE other )
{
if ( this.ID == other.ID ) return true;
return false;
}
}
After I did this, the above RemoveAll() answer by Reed Copsey worked perfectly for me.
See: http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx
So I have a list of objects with a number of properties. Among these properties are name and id. Let's call this object ExtendedObject. I've also declared a new List of different objects that have only the properties name and id. Let's call this object BasicObject.
What I'd like to do is convert or copy (for lack of better words) the List of ExtendedObject objects to a list of BasicObject objects. I know C# Lists have a lot of interesting methods that can be useful, so I wondered if there were an easy way to say something to the effect of:
basicObjectList = extendedObjectList.SomeListMethod<BasicObject>(some condition here);
But I realize it may end up looking nothing like that. I also realize that I could just loop through the list of ExtendedObjects, create a new BasicObject from each ExtendedObject's name and id, and push it onto a list of BasicObjects. But I was hoping for something a little more elegant than that.
Does anyone have any ideas? Thanks very much.
It depends on exactly how you'd construct your BasicObject from an ExtendedObject, but you could probably use the ConvertAll method:
List<BasicObject> basicObjectList =
extendedObjectList.ConvertAll(x => new BasicObject
{
id = x.id,
name = x.name
});
Or, if you prefer, you could use the LINQ Select method and then convert back to a list:
List<BasicObject> basicObjectList =
extendedObjectList.Select(x => new BasicObject
{
id = x.id,
name = x.name
}).ToList();
if you are on .NET 3.5 or greater this could be done by using LINQ projections:
basicObjectList = extendedObjectList.Select(x => new BasicObject { Id=x.Id, Name=x.Name})
var basicObjectList = extendedObjectList.Select(eo => new BasicObject { name = eo.name, id = eo.id });
I think that the OP's suggestion of "BasicObject" was just a pseudonym for a resulting object with a specific subset of properties from the original set. Anonymous types are your friend (as indicated by #mumtaz).
Assuming the following extendedObjectList if of IEnumerable<T> (including a List):
// "var" used so that runtime infers the type automatically
var subset = extendedObjectList
// you can use any Linq based clause for filtering
.Where(a => <conditions>)
// where the runtime creates a runtime anonymous type to describe your "BasicObject"
.Select(a => new { a.Property1, a.Property2, a.Property3 })
// conversion to a List collection of your anonymous type
.ToList();
At this point, subset contains a List of an anonymous (runtime) type that contains three properties - Property1, Property2, Property3.
You can manipulate this resulting list as follows:
// execute an anonymous delegate (method) for each of the new anonymous objects
subset.ForEach
(
basicObject =>
{
Console.WriteLine("Property1 - {0}", basicObject.Property1);
Console.WriteLine("Property2 - {0}", basicObject.Property2);
Console.WriteLine("Property3 - {0}", basicObject.Property3);
}
);
// grab the first object off the list
var firstBasicObject = subset.First();
// sort the anonymously typed list
var sortedSubset = subset.OrderBy(a => a.Property1).ToList();
Once the runtime has resolved the new object (of any combination of properties from the source object), you can use it virtually any way that you wish.
For Linq-to-Sql applications (using IQueryable<T>), the Select statement can be used to obtain specific column data (instead of the entire row), thereby creating an anonymous type to describe a subset of column data for a given row.
Lets say we have a simple class
public class Foo
{
public string FooName;
}
Now we want to do some simple work on it.
public void SomeCallerMethod(List<Foo> listOfFoos)
{
string[] fooNames = listOfFoo. // What to do here?
}
If I even knew what method to call, I could probably find the rest of the peices.
You want to transform a list of your class into an array of strings. The ideal method for this is Select, which operates on each element on the enumerable and builds a new enumerable based on the type you return.
You need to put a lambda expression into the select method that returns the name, which will simply be "for each element, select the name".
You then need to cast the output as an array.
string[] fooNames = listOfFoos.Select(foo => foo.FooName).ToArray();
Or, using the other syntax:
string[] fooNames = (from foo in listOfFoos
select foo.FooName).ToArray();