Dictionary get max key values else print all - c#

I've checked many solutions on different sites but couldn't find what I was looking for. I'm working on a dictionary object with different Values against Keys. The structure is as follows:
Key Value
6 4
3 4
2 2
1 1
If they dictionary contains elements like this, the output should be 6 and 3, if Key (6) has the highest value, it should print only 6. However, if all the values are same against each key, it should print all the keys.
Trying to use the following but it only prints the highest Value.
var Keys_ = dicCommon.GroupBy(x => x.Value).Max(p => p.Key);
Any ideas

Instead of using Max(x=>x.Key) use .OrderByDescending(x=>x.Key) and .FirstOrDefault() that will give you the group that has the max value. You then can itterate over the group and display whatever you need.
var dicCommon = new Dictionary<int, int>();
dicCommon.Add(6, 4);
dicCommon.Add(3, 4);
dicCommon.Add(2, 2);
dicCommon.Add(1, 1);
var maxGroup = dicCommon.GroupBy(x => x.Value).OrderByDescending(x => x.Key).FirstOrDefault();
foreach (var keyValuePair in maxGroup)
{
Console.WriteLine("Key: {0}, Value {1}", keyValuePair.Key, keyValuePair.Value);
}
Run Code

First off a query can't return one and more than one result at the same time.So you need to pick one.
In this case if you want all Keys that has the highest corresponding Value, you can sort the groups based on Value then just get the first group which has the highest Value:
var Keys_ = dicCommon.GroupBy(x => x.Value)
.OrderByDescending(g => g.Key)
.First()
.Select(x => x.Key)
.ToList();

var keys = String.Join(",", dicCommon
.OrderByDescending(x=>x.Value)
.GroupBy(x => x.Value)
.First()
.Select(x=>x.Key));

You’re almost there:
dicCommon.GroupBy(x => x.Value)
.OrderByDescending(pair => pair.First().Value)
.First().Select(pair => pair.Key).ToList()
GroupBy returns an enumerable of IGrouping. So sort these descending by value, then get the first, and select the key of each containing element.
Since this requires sorting, the runtime complexity is not linear, although we can easily do that. One way would be figuring out the maximum value first and then getting all the keys where the value is equal to that:
int maxValue = dicCommon.Max(x => x.Value);
List<int> maxKeys = dicCommon.Where(x => x.Value == maxValue).Select(x => x.Key).ToList();

Related

How to modify string list for duplicate values?

I am working on project which is asp.net mvc core. I want to replace string list of duplicate values to one with comma separated,
List<string> stringList = surveylist.Split('&').ToList();
I have string list
This generate following output:
7=55
6=33
5=MCC
4=GHI
3=ABC
1003=DEF
1003=ABC
1=JKL
And I want to change output like this
7=55
6=33
5=MCC
4=GHI
3=ABC
1003=DEF,ABC
1=JKL
Duplicate items values should be comma separated.
There are probably 20 ways to do this. One simple one would be:
List<string> newStringList = stringList
.Select(a => new { KeyValue = a.Split("=") })
.GroupBy(a => a.KeyValue[0])
.Select(a => $"{a.Select(x => x.KeyValue[0]).First()}={string.Join(",", a.Select(x => x.KeyValue[1]))}")
.ToList();
Take a look at your output. Notice that an equal sign separates each string into a key-value pair. Think about how you want to approach this problem. Is a list of strings really the structure you want to build on? You could take a different approach and use a list of KeyValuePairs or a Dictionary instead.
If you really need to do it with a List, then look at the methods LINQ's Enumerable has to offer. Namely Select and GroupBy.
You can use Select to split once more on the equal sign: .Select(s => s.Split('=')).
You can use GroupBy to group values by a key: .GroupBy(pair => pair[0]).
To join it back to a string, you can use a Select again.
An end result could look something like this:
List<string> stringList = values.Split('&')
.Select(s => {
string[] pair = s.Split('=');
return new { Key = pair[0], Value = pair[1] };
})
.GroupBy(pair => pair.Key)
.Select(g => string.Concat(
g.Key,
'=',
string.Join(
", ",
g.Select(pair => pair.Value)
)
))
.ToList();
The group contains pairs so you need to select the value of each pair and join them into a string.

Highhest Number's key in a GroupBy

I have a simple class:
class Balls
{
public int BallType;
}
And i have a really simple list:
var balls = new List<Balls>()
{
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 2}
};
I've used GroupBy on this list and I want to get back the key which has the highest count/amount:
After I used x.GroupBy(q => q.BallType) I tried to use .Max(), but it returns 3 and I need the key which is 1.
I also tried to use Console.WriteLine(x.GroupBy(q => q.Balltype).Max().Key); but it throws System.ArgumentException.
Here's what I came up with:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
You group by the BallType, order by the count of items in the group, get the last value (since order by is in an ascending order, the most common value would be the last) and then return it's key
Some came up with the idea to order the sequence:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
Apart from that it is more efficient to OrderByDescending and then take the FirstOrDefault, you also get in trouble if your collection of Balls is empty.
If you use a different overload of GroupBy, you won't have these problems
var mostCommonBallType = balls.GroupBy(
// KeySelector:
k => k.BallType,
// ResultSelector:
(ballType, ballsWithThisBallType) => new
{
BallType = ballType,
Count = ballsWithThisBallType.Count(),
})
.OrderByDescending(group => group.Count)
.Select(group => group.BallType)
.FirstOrDefault();
This solves the previously mentioned problems. However, if you only need the 1st element, why would you order the 2nd and the 3rd element? Using Aggregate instead of OrderByDescending will enumerate only once:
Assuming your collection is not empty:
var result = ... GroupBy(...)
.Aggregate( (groupWithHighestBallCount, nextGroup) =>
(groupWithHighestBallCount.Count >= nextGroup.Count) ?
groupWithHighestBallCount : nextGroup)
.Select(...).FirstOrDefault();
Aggregate takes the first element of your non-empty sequence, and assigns it to groupWithHighestBallCount. Then it iterates over the rest of the sequence, and compare this nextGroup.Count with the groupWithHighestBallCount.Count. It keeps the one with the hightes value as the next groupWithHighestBallCount. The return value is the final groupWithHighestBallCount.
See that Aggregate only enumerates once?

How to do in this in Linq C#

So far, I have this:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)));
Configuration folder will contain pairs of files:
abc.json
abc-input.json
def.json
def-input.json
GetReportName() method strips off the "-input" and title cases the filename, so you end up with a grouping of:
Abc
abc.json
abc-input.json
Def
def.json
def-input.json
I have a ReportItem class that has a constructor (Name, str1, str2). I want to extend the Linq to create the ReportItems in a single statement, so really something like:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
**.Select(x => new ReportItem(x.Key, x[0], x[1]));**
Obviously last line doesn't work because the grouping doesn't support array indexing like that. The item should be constructed as "Abc", "abc.json", "abc-input.json", etc.
If you know that each group of interest contains exactly two items, use First() to get the item at index 0, and Last() to get the item at index 1:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
.Where(g => g.Count() == 2) // Make sure we have exactly two items
.Select(x => new ReportItem(x.Key, x.First(), x.Last()));
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x))).Select(x => new ReportItem(x.Key, x.FirstOrDefault(), x.Skip(1).FirstOrDefault()));
But are you sure there will be exactly two items in each group? Maybe has it sence for ReportItem to accept IEnumerable, not just two strings?

Converting Collection of Strings to Dictionary

This is probably a simple question, but the answer is eluding me.
I have a collection of strings that I'm trying to convert to a dictionary.
Each string in the collection is a comma-separated list of values that I obtained from a regex match. I would like the key for each entry in the dictionary to be the fourth element in the comma-separated list, and the corresponding value to be the second element in the comma-separated list.
When I attempt a direct call to ToDictionary, I end up in some kind of loop that appears to kick me of the BackgroundWorker thread I'm in:
var MoveFromItems = matches.Cast<Match>()
.SelectMany(m => m.Groups["args"].Captures
.Cast<Capture>().Select(c => c.Value));
var dictionary1 = MoveFromItems.ToDictionary(s => s.Split(',')[3],
s => s.Split(',')[1]);
When I create the dictionary manually, everything works fine:
var MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
if(!MoveFroms.ContainsKey(sKey))
MoveFroms[sKey.ToUpper()] = sVal;
}
I appreciate any help you might be able to provide.
The problem is most likely that the keys have duplicates. You have three options.
Keep First Entry (This is what you're currently doing in the foreach loop)
Keys only have one entry, the first one that shows up - meaning you can have a Dictionary:
var first = MoveFromItems.Select(x => x.Split(','))
.GroupBy(x => x[3])
.ToDictionary(x => x.Key, x => x.First()[1]);
Keep All Entries, Grouped
Keys will have more than one entry (each key returns an Enumerable), and you use a Lookup instead of a Dictionary:
var lookup = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);
Keep All Entries, Flattened
No such thing as a key, simply a flattened list of entries:
var flat = MoveFromItems.Select(x => x.Split(','))
.Select(x => new KeyValuePair<string,string>(x[3], x[1]));
You could also use a tuple here (Tuple.Create(x[3], x[1]);) instead.
Note: You will need to decide where/if you want the keys to be upper or lower case in these cases. I haven't done anything related to that yet. If you want to store the key as upper, just change x[3] to x[3].ToUpper() in everything above.
This splits each item and selects key out of the 4th split-value, and value out of the 2nd split-value, all into a dictionary.
var dictionary = MoveFromItems.Select(s => s.Split(','))
.ToDictionary(split => split[3],
split => split[1]);
There is no point in splitting the string twice, just to use different indices.
This would be just like saving the split results into a local variable, then using it to access index 3 and 1.
However, if indeed you don't know if keys might reoccur, I would go for the simple loop you've implemented, without a doubt.
Although you have a small bug in your loop:
MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
// sKey might not exist as a key
if (!MoveFroms.ContainsKey(sKey))
//if (!MoveFroms.ContainsKey(sKey.ToUpper()))
{
// but sKey.ToUpper() might exist!
MoveFroms[sKey.ToUpper()] = sVal;
}
}
Should do ContainsKey(sKey.ToUpper()) in your condition as well, if you really want the key all upper cases.
This will Split each string in MoveFromItems with ',' and from them make 4th item (3rd Index) as Key and 2nd item(1st Index) as Value.
var dict = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);

How can I order a Dictionary in C#?

edit: Thanks Jason, the fact that it was a dictionary isn't that important. I just wanted the runtime to have a low runtime. Is that LINQ method fast? Also, I know this is off topic but what does the n => n mean?
I have a list of numbers and I want to make another list with the numbers that appear most at the beginning and the least at the end.
So what I did was when through the list and checked if the number x was in the dictionary. If it wasn't then I made the key x and the value one. If it was then I changed the value to be the value plus one.
Now I want to order the dictionary so that I can make a list with the ones that appear the most at the beginning and the least at the end.
How can I do that in C#?
ps. runtime is very important.
So it sounds like you have a Dictionary<int, int> where the key represents some integer that you have in a list and corresponding value represents the count of the number of times that integer appeared. You are saying that you want to order the keys by counts sorted in descending order by frequency. Then you can say
// dict is Dictionary<int, int>
var ordered = dict.Keys.OrderByDescending(k => dict[k]).ToList();
Now, it sounds like you started with a List<int> which are the values that you want to count and order by count. You can do this very quickly in LINQ like so:
// list is IEnumerable<int> (e.g., List<int>)
var ordered = list.GroupBy(n => n)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.ToList();
Or in query syntax
var ordered = (from n in list
group n by n into g
orderby g.Count() descending
select g.Key).ToList();
Now, if you need to have the intermediate dictionary you can say
var dict = list.GroupBy(n => n)
.ToDictionary(g => g.Key, g => g.Count());
var ordered = dict.Keys.OrderByDescending(k => dict[k]).ToList();
Use the GroupBy extension on IEnumerable() to group the numbers and extract the count of each. This creates the dictionary from the list and orders it in one statement.
var ordered = list.GroupBy( l => l )
.OrderByDescending( g => g.Count() )
.ToDictionary( g => g.Key, g.Count() );
You may also consider using SortedDictionary.
It sorts the items on the basis of key, while insertion. more..
List<KeyValuePair<type, type>> listEquivalent =
new List<KeyValuePair<type, type>>(dictionary);
listEquivalent.Sort((first,second) =>
{
return first.Value.CompareTo(second.Value);
});
Something like that maybe?
edit: Thanks Jason for the notice on my omission

Categories