LINQ casting during enumeration - c#

I have a List<string>
List<string> students;
students.Add("Rob");
students.Add("Schulz");
and a Dictionary<string,string>
Dictionary<string, string> classes= new Dictionary<string, string>();
classes.Add("Rob", "Chemistry");
classes.Add("Bob", "Math");
classes.Add("Holly", "Physics");
classes.Add("Schulz", "Botany");
My objective now is to get a List with the values - Chemistry and Botany - for which I am using this
var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => new { tag = students[k] });
While trying to enumerate the values - I am able to obtain - tag=Chemistry & tag=Botany...while I want just Chemistry and Botany.
What is the appropriate casting to be applied? Is there a better way to get to these values?

You only have to write:
var filteredList = students.Where(student => classes.ContainsKey(student));
Here, student is a string, since students is a List<string>, so you only have to apply Where(). The result will be an IEnumerable<string>.
You can apply ToList() if you want to exhaust the enumerable into another List<string>:
var filteredList = students.Where(student => classes.ContainsKey(student)).ToList();
If you want a list of classes (it's not clear from the code in your question), then you have to apply Select() to project classes from students:
var filteredList = students.Where(student => classes.ContainsKey(student))
.Select(student => classes[student]);

try:
var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => students[k]);

var filteredList = students.Where(k => classes.ContainsKey(k))
.Select(k => students[k]);

Related

Searching a List of List

I have the following object:
List<List<MyObj>> lst;
I need to find a list of all the objects (List< MyObj >) in the inner list, that has ID equal 1.
I tried:
lst.Where(x => x.FindAll(y=> y.ID== "1"));
lst.FindAll(x => x.FindAll(y=> y.ID== "1"));
and also tried to use Any() but no luck.
You can use SelectMany() to flatten the lists and then filter the elements:
var result = lst.SelectMany(x => x).Where(y => y.ID == "1").ToList();
List<MyObj> list1 = lst.SelectMany(x => x.Where(y=> y.ID== "1")).ToList();
or
List<List<MyObj>> list2 = lst.Where(x => x.Any(y=> y.ID== "1")).ToList();
depending on what it is you want as a result..
SelectMany is your friend. Example:
var listOfListsOfStrings = new List<List<string>>();
listOfListsOfStrings.Add(new List<string>() {"a", "b"});
listOfListsOfStrings.Add(new List<string>() {"c", "d"});
var allStrings = listOfListsOfStrings.SelectMany(s => s);
Console.WriteLine(string.Join(", ", allStrings.ToArray())); //prints: a, b, c, d
So in your case you just need:
lst.SelectMany(x => x).Where(y => y.ID == "1")
Let me add another option to the already good set of options. It is using Hashset<T> for search, by converting the internal List<T>, this would help when data size is more, since Hashset<T> has O(1) search instead of O(N) for List<T>
List<List<MyObj>> lst;
var result = lst.where(x =>
{
// Assuming ID to be string type
var hashset = new Hashset<string>(x.Select(y => y.ID));
return hashset.Contains("1");
}
);
In case you are not keen to do conversion, then following code would do:
var result = lst.where(x => x.Any(y => y.ID == "1"));
result will be of type List<List<MyObj>>, which will be filtered, currently we are are supplying Func<T,bool> to the Enumerable.Where, when data is supplied at run-time, then its easier to construct Expression<Func<T,bool>>, which gets compiled at run-time into correct Func<T,bool> delegate to filter actual list

C# Compare one List string with substring of other list string

I have two lists
List<string> ingoreEducationKeywords= new List<string>(){"Uni", "School", "College",};
List<string> userEducation= new List<string>(){"MCS", "BCS", "School of Arts","College of Medicine"};
Now I want to get a list which has no substring from the ignore list.
require list {"MCS", "BCS"}
That's a relatively straightforward query that can be constructed with Any or All, depending on your preferences:
var res = userEducation
.Where(s => !ingoreEducationKeywords.Any(ignored => s.Contains(ignored)))
.ToList();
or
var res = userEducation
.Where(s => ingoreEducationKeywords.All(ignored => !s.Contains(ignored)))
.ToList();
If the lists are very large, you could improve performance by using regex to match all words simultaneously:
var regex = new Regex(
string.Join("|", ingoreEducationKeywords.Select(Regex.Escape))
);
var res = userEducation.Where(s => !regex.IsMatch(s)).ToList();
Demo.
It's a matter of phrasing what you want in a way that leads to a natural translation into LINQ:
You want items from userEducation (that suggests you'll start with userEducation)
Where none of ignoreEducationKeywords are substrings.
"None" is equivalent to "not any"
To check for substrings you can use Contains
That leads to:
var query = userEducation
.Where(candidate => !ignoredKeyWords.Any(ignore => candidate.Contains(ignore)));
The same thought process can help in many other queries.
Another option would be to create your own None extension method, assuming you're using LINQ to Objects:
public static class Extensions
{
public static bool None(this IEnumerable<T> source, Func<T, bool> predicate)
=> !source.Any(predicate);
}
Then you could rewrite the query without the negation:
var query = userEducation
.Where(candidate => ignoredKeyWords.None(ignore => candidate.Contains(ignore)));
You can use Where, Any and Contains:
var list = userEducation.Where(ed => !ingoreEducationKeywords.Any(ik => ed.Contains(ik)));
It searches all occurences in userEducation where the education does not have any match in ingoreEducationKeywords.
List<string> ingoreEducationKeywords = new List<string>() { "Uni", "School", "College", };
List<string> userEducation = new List<string>() { "MCS", "BCS", "School of Arts", "College of Medicine" };
var result = userEducation.Where(r => !ingoreEducationKeywords.Any(t => r.Contains(t))).ToList();

How extract properties from several objects with LINQ

I often find myself doing the below to extract properties from a list of objects just to create an aggregated list. How would this be expressed with LINQ?
var totalErrors =
new Dictionary<string, string>();
foreach (var res in results)
{
foreach (var err in res.Errors)
{
totalErrors
.Add(err.Key, err.Value);
}
}
return totalErrors;
You can use SelectMany and ToDictionary methods:
var result = results
.SelectMany(x => x.Errors) // get all Errors in one sequence
.ToDictionary(x => x.Key, x => x.Value); // create new dictionary based on this Enumerable
SelectMany() projects each element of a sequence to an IEnumerable<T> and flattens the resulting sequences into one sequence. And ToDictionary() creates a Dictionary<TKey, TValue> from an IEnumerable<T> according to a specified key selector function.
You can do an aggregation on two levels with SelectMany, like this:
var totalErrors = results
.SelectMany(r => r.Errors)
.ToDictionary(e => e.Key, e => e.Value);
SelectMany "flattens" a collection of collections into a single level, at which point you can apply ToDictionary to a flattened list.
You can use SelectMany :
var allErrors = Results.SelectMany( res=>res.Errors );
//foreach( var error in allErrors )...
var dictionary = allErrors.ToDictionary( x=>x.Key, x=> x.Value );

Using LINQ to build a Dictionary from a List of delimited strings

I have a list of strings that look like this:
abc|key1|486997
def|key1|488979
ghi|key2|998788
gkl|key2|998778
olz|key1|045669
How can I use LINQ and ToDictionary to produce a Dictionary<string, List<string>> that looks like
key1 : { abc|key1|486997, def|key1|488979, olz|key1|045669 }
key2 : { ghi|key2|998788, gkl|key2|998778 }
Basically I want to be able to extract the second element as the key use ToDictionary() to create the dictionary in one go-round.
I'm currently doing this ..
var d = new Dictionary<string, List<string>>();
foreach(var l in values)
{
var b = l.Split('|');
var k = b.ElementAtOrDefault(1);
if (!d.ContainsKey(k))
d.Add(k, new List<string>());
d[k].Add(l);
}
I've seen the questions on building dictionaries from a single string of delimited values, but I'm
wondering if there's an elegant way to do this when starting with a list of delimited strings instead.
var list = new []
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var dict = list.GroupBy(x => x.Split('|')[1])
.ToDictionary(x => x.Key, x => x.ToList());
You can also transform it to a lookup (that is very similary to a Dictionary<K,IEnumerable<V>>) in one shot:
var lookup = list.ToLookup(x => x.Split('|')[1]);
var data = new[]
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var dictionary = data.Select(row => row.Split('|'))
.GroupBy(row => row[1])
.ToDictionary(group => group.Key, group => group);
If your data is guaranteed to be consistent like that, you could do something like this:
var data = new[]
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var items = data
.GroupBy(k => k.Split('|')[1])
.ToDictionary(k => k.Key, v => v.ToList());

LINQ using C# question

I have a List<T> that contains some user defined class data.
I want to find the unique instances of a combination of 2 data fields.
For example, if the entries contain the fields Name and Age, I want the unique cases of the Name and Age combination, e.g. Darren 32, should only be retrieved once, even if it is in the list multiple times.
Can this be achieved with LINQ?
Thanks.
You need to extract only these data fields and make them unique:
var result = list
.Select(x => new { Age = a.Age, Name = x.Name})
.Distinct();
This creates a IEnumerable of a anonymous type which contains a Age and Name property.
If you need to find the items behind the unique data, you need GroupBy. This will provide the list with the single items behind each group.
var result = list
.GroupBy(x => new { Age = a.Age, Name = x.Name});
foreach (var uniqueItem in result )
{
var age = uniqueItem.Key.Age;
var name = uniqueItem.Key.Name;
foreach (var item in uniqueItem)
{
//item is a single item which is part of the group
}
}
myList.Select(l => new { l.Name, l.Age })
.Distinct()
.ToDictionary(x => x.Name, x => x.Age)
You'll have to write your own equality comparer, and use Linq's Distinct function.
Have a look at the Distinct extension method
Easy:
var people = new List<Person>();
// code to populate people
var uniqueNameAges =
(from p in people
select new { p.Name, p.Age }).Distinct();
And then to a dictionary:
var dictionary =
uniqueNameAges
.ToDictionary(x => x.Name, x => x.Age);
Or to a lookup (very much like Dictionary<string, IEnumerable<int>> in this case):
var lookup =
uniqueNameAges
.ToLookup(x => x.Name, x => x.Age);
If you then have people named "John" with distinct ages then you could access them like so:
IEnumerable<int> ages = lookup["John"];

Categories