I'm new to lambdas and they seemed fairly straight-forward until I tried to do something more complex.
I have this dictionary.
Dictionary<int, int> dict = new Dictionary<int,int>();
of which I want to obtain the key of the key-val pair with the largest value. What I tried is:
dict.Keys.Max(g => dict[g])
The reasoning being that out of the list of Keys, pick that one for which dict[key] is largest. However, this picks the largest value itself, rather than its corresponding key.
dict.Keys.OrderByDescending(g => dict[g]).First() will accomplish what you want, but may be inefficient for large dictionaries. MaxBy in John Skeet's MoreLinq will do exactly what you want efficiently.
var maxValue = dict.Max((maxPair) => maxPair.Value);
var maxPairs = dict.Where((pair) => pair.Value == maxValue);
This will give you a list of all of the pairs that have the maximum value.
If you just want the keys, you can do this afterwards:
var maxKeys = maxPairs.Select((pair) => pair.Key);
I decided to add an answer based on my thoughts on McKay's. This will perform very fast given the standard LINQ methods, provides just the key:
var maxValue = dict.Max(p => p.Value);
var keys = dict.Where(p => p.Value == maxValue).Select(p => p.Key);
Now, if the OP knows that there is always just one key (no duplicate values) then an improvement (very small) would be to use First with this as due to lazy evaluation only the elements up to the one with the maximum value would be evaluated after all were evaluated to first find the maximum value:
var key = dict.Where(p => p.Value == maxValue).First().Key;
dict.OrderBy(v => v.Value).Last().Key;
should do it. Basically you are ordering the KeyValuePair by value and picking the last one which will be the maximum. And within the last one you are only interested in the Key.
Related
This question already has answers here:
How to Count Duplicates in List with LINQ
(7 answers)
Closed 2 years ago.
I currently have what I believe is a lambda function with C# (fairly new to coding & haven't used a lambda function before so go easy), which adds duplicate strings (From FilteredList) in a list and counts the number of occurrences and stores that value in count. I only want the most used word from the list which I've managed to do by the "groups.OrderBy()... etc) line, however I'm pretty sure that I've made this very complicated for myself and very inefficient. As well as by adding the dictionary and the key value pairs.
var groups =
from s in FilteredList
group s by s into g
// orderby g descending
select new
{
Stuff = g.Key,
Count = g.Count()
};
groups = groups.OrderBy(g => g.Count).Reverse().Take(1);
var dictionary = groups.ToDictionary(g => g.Stuff, g => g.Count);
foreach (KeyValuePair<string, int> kvp in dictionary)
{
Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
}
Would someone please either help me through this and explain a little bit of this too me or at least point me in the direction of some learning materials which may help me better understand this.
For extra info: The FilteredList comes from a large piece of external text, read into a List of strings (split by delimiters), minus a list of string stop words.
Also, if this is not a lambda function or I've got any of the info in here incorrect, please kindly correct me so I can fix the question to be more relevant & help me find an answer.
Thanks in advance.
Yes, I think you have overcomplicated it somewhat.. Assuming your list of words is like:
var words = new[] { "what's", "the", "most", "most", "most", "mentioned", "word", "word" };
You can get the most mentioned word with:
words.GroupBy(w => w).OrderByDescending(g => g.Count()).First().Key;
Of course, you'd probably want to assign it to a variable, and presentationally you might want to break it into multiple lines:
var mostFrequentWord = words
.GroupBy(w => w) //make a list of sublists of words, like a dictionary of word:list<word>
.OrderByDescending(g => g.Count()) //order by sublist count descending
.First() //take the first list:sublist
.Key; //take the word
The GroupBy produces a collection of IGroupings, which is like a Dictionary<string, List<string>>. It maps each word (the key of the dictionary) to a list of all the occurrences of that word. In my example data, the IGrouping with the Key of "most" will be mapped to a List<string> of {"most","most","most"} which has the highest count of elements at 3. If we OrderByDescending the grouping based on the Count() of each of the lists then take the First, we'll get the IGrouping with a Key of "most", so all we need to do to retrieve the actual word is pull the Key out
If the word is just one of the properties of a larger object, then you can .GroupBy(o => o.Word). If you want some other property from the IGrouping such as its first or last then you can take that instead of the Key, but bear in mind that the property you end up taking might be different each time unless you enforce ordering of the list inside the grouping
If you want to make this more efficient than you can install MoreLinq and use MaxBy; getting the Max word By the count of the lists means you can avoid a sort operation. You could also avoid LINQ and use a dictionary:
string[] words = new[] { "what", "is", "the", "most", "most", "most", "mentioned", "word", "word" };
var maxK = "";
var maxV = -1;
var d = new Dictionary<string, int>();
foreach(var w in words){
if(!d.ContainsKey(w))
d[w] = 0;
d[w]++;
if(d[w] > maxV){
maxK = w;
maxV = d[w];
}
}
Console.WriteLine(maxK);
This keeps a dictionary that counts words as it goes, and will be more efficient than the LINQ route as it needs only a single pass of the word list, plus the associated dictionary lookups in contrast to "convert wordlist to list of sublists, sort list of sublists by sublist count, take first list item"
This should work:
var mostPopular = groups
.GroupBy(item => new {item.Stuff, item.Count})
.Select(g=> g.OrderByDescending(x=> x.Count).FirstOrDefault())
.ToList();
OrderByDescending along with .First() combines your usage of OrderBy, Reverse() and Take.
First part is a Linq operation to read the groups from the FilteredList.
var groups =
from s in FilteredList
group s by s into g
// orderby g descending
select new
{
Stuff = g.Key,
Count = g.Count()
};
The Lambda usage starts when the => signal is used. Basically means it's going to be computed at run time and an object of that type/format is to be created.
Example on your code:
groups = groups.OrderBy(g => g.Count).Reverse().Take(1);
Reading this, it is going to have an object 'g' that represents the elements on 'groups' with a property 'Count'. Being a list, it allows the 'Reverse' to be applied and the 'Take' to get the first element only.
As for documentation, best to search inside Stack Overflow, please check these links:
C# Lambda expressions: Why should I use them? - StackOverflow
Lambda Expressions in C# - external
Using a Lambda Expression Over a List in C# - external
Second step: if the data is coming from an external source and there are no performance issues, you can leave the code to refactor onwards. A more detail data analysis needs to be made to ensure another algorithm works.
I have a dictionary in C#.
Dictionary originalMap = new Dictionary<string, List<CustomObject>>();
originalMap contents:
"abc" -> {CustomObject1, CustomeObject2, ..CustomObject100};
"def" -> {CustomObject101,CustomObject102, CustomObject103};
Now, finally I want to make sure that the count of all custome objects above does not exceed a limit - say 200.
While doing this, I want to make sure that I take top 200 ranked object (sorted by Score porperty).
I tried below but it doesn't work. It returns same number of objects.
var modifiedMap = new Dictionary<string, IList<CustomObject>>(CustomObject);
modifiedMap = originalMap.OrderByDescending(map => map.Value.OrderByDescending(customObject => customObject.Score).ToList().Take(200))
.ToDictionary(pair => pair.Key, pair => pair.Value);
Any help will be appreciated.
Thank you.
You're only performing the limit part while doing the ordering - not when you actually create the new dictionary.
It sounds like you want something like:
var modifiedMap = originalMap.ToDictionary(
pair => pair.Key,
pair => pair.Value.OrderByDescending(co => co.Score).Take(200).ToList());
Note that ordering the dictionary entries themselves would be pointless, as Dictionary<TKey, TValue> is inherently not an ordered collection.
I have
IDictionary<string,object> d1;
Dictionary<string, object> d2;
I need to remove from d1 all entries that are not in d2.
I know I can do this with a for loop etc but that's so last century; I want to do it right.
I got to
d1.Where(x => {return d2.ContainsKey(x.key);});
but dont know what to do next
LINQ isn't designed to modify existing elements - but you could always create a new dictionary. For example:
d1 = d1.Where(x => d2.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value);
Or:
d1 = d1.Keys.Intersect(d2.Keys)
.ToDictionary(key => x.Key, key => d1[key]);
As others have said, if you're more keen on doing a Remove operation, I'd just loop. For example:
foreach (var key in d1.Keys.Except(d2.Keys).ToList())
{
d1.Remove(key);
}
(I'm not sure why you used a statement lambda in your sample code, by the way.)
LINQ is for querying. The information you're querying is the keys from d1 that are not in d2. Other than missing a NOT (unless you didn't mean to say "not" in your requirements). you already have that. When it comes to actually doing something, that's best done with a foreach loop, not LINQ:
foreach(var pair in d1.Where(x=> !d2.ContainsKey(x.Key)).ToList())
{
d1.Remove(pair.Key)
}
Note that the ToList is needed to ensure that you are not modifying a collection while iterating it.
I have a dictionary with non unique values and I want to count the matches of a string versus the values.
Basically I now do dict.ContainsValue(a) to get a bool telling me if the string a exists in dict, but I want to know not only if it exists but how many times it exists (and maybee even get a list of the keys it exists bound to)
Is there a way to do this using dictionary, or should I look for a different collection?
/Rickard Haake
To get the number of instances of the value you could do something like this:
dict.Values.Count(v => v == a);
To find the keys that have this value you could do this:
dict.Where(kv => kv.Value == a).Select(kv => kv.Key);
To get the count use Values.Count:
int count = dict.Values.Count(x => x == "foo");
To get the keys I prefer the query syntax:
var keys = from kvp in dict
where kvp.Value == "foo"
select kvp.Key;
Note that this will require scanning the entire dictionary. For small dictionaries or infrequent lookups this may not be a problem.
If you are making many lookups you may wish to maintain a second dictionary that maps the values to the keys. Whilst this will speed up lookups, it will slow down modifications as both dictionaries will need updating for each change.
what about using LINQ: if a is the value you're looking for, the the code could be
dict.Values.Where(v => v == a).Count();
I need to use a list pulled from sql, list is built from
Dictionary<Int32, String> measurementTypes = this.GetIndicatorTypes(MeasurementTypeFilter.All);
is ther a way to retrive the key using the string.
Something like
TypeID = measurementTypes.contains("GEN");
Well, it'll be slow (i.e. O(n)), but you can do:
var keys = measurementTypes.Where(pair => pair.Value == "GEN")
.Select(pair => pair.Key);
That will give you a sequence of pairs which have the given value. There could be 0, 1 or many matches. From there you can pick the first matching key etc - whatever you need. Using First or Single would be appropriate if you think there will be at least one or exactly one; FirstOrDefault would return 0 if there were no matches, which may not be appropriate for you if 0 could also be a valid key.
TypeID = measurementTypes.Values.Where(v => v.Equals("GEN")).FirstOrDefault();
You can use LINQ to help you out there.
TypeID = measurementTypes.Where(kvp => kvp.Value == "GEN")
.Select(kvp => kvp.Key)
.FirstOrDefault()