0

I want to get the keys of the highest values in a dictionary {'a' : 1, 'b' : 2, 'c' : 2}. In this case I want it to return 'b' but use of

weight.Aggregate((l, r) => l.Value > r.Value ? l : r).Key;

returns the last largest value i.e. 'c'.

  1. Is there a way to return the key of the first largest value in my dictionary? =>'b'
  2. Also, is it possible to return an array of keys of the max tied values? => new char[]{'b', 'c'}
  3. Which Key-Value data structure should I use instead?

EDIT: Since it has caused quite a stir in the comments, I mean the first largest value in terms of insertion order.

user14773854
  • 303
  • 4
  • 10
  • 1
    `Dictionary` are not stable. – Daniel A. White Feb 16 '22 at 15:14
  • 5
    Dictionarys do not have a concept of "first". A software update, and suddenly it might start returning `c` instead, or alternating between options every time you run it. – Joel Coehoorn Feb 16 '22 at 15:15
  • @JoelCoehoorn there is an OrderedDictionary for that, never really used it. – Pedro Rodrigues Feb 16 '22 at 15:16
  • @PedroRodrigues are you talking about Python or C#? – user14773854 Feb 16 '22 at 15:18
  • @user14773854 C#, python dictionaries have been ordered for a while. https://learn.microsoft.com/en-us/dotnet/api/system.collections.specialized.ordereddictionary?view=net-6.0 – Pedro Rodrigues Feb 16 '22 at 15:19
  • Dictionary dict = new Dictionary{{'a', 1}, {'b',2}, {'c', 2}}; var results = dict.OrderByDescending(x => x.Value).GroupBy(x => x.Value).FirstOrDefault().FirstOrDefault(); – jdweng Feb 16 '22 at 15:20
  • Also [this](https://stackoverflow.com/q/1396718/2501279) and [this](https://stackoverflow.com/q/2722767/2501279) can point into right direction. – Guru Stron Feb 16 '22 at 15:53
  • @GuruStron Unfortuately `MaxBy` is not supported on my compiler... – user14773854 Feb 16 '22 at 15:56
  • @user14773854 your `Aggregate` in question is effectively `MaxBy` so it is not hard to implement/adapt that to the grouped values. Though it will not fix the issue that C# dictionary does not guarantee to preserve insertion order. – Guru Stron Feb 16 '22 at 15:57
  • Will you modify the dictionary after creation(add/delete)? If so, it might be that you first get 'b' and later you will get a different char from the highest-value-group. You want the first in insertion order or the first alphabetically? Also, do you need the fast lookup capability of a dictionary at all? Otherwise you are free to build a simple `List` which makes it simple and reliable. – Tim Schmelter Feb 16 '22 at 15:59
  • If you want MaxBy and dont want to write it yourself you can install MoreLinq or use .NET 6 – Caius Jard Feb 16 '22 at 16:02
  • 1
    So.. I'm curious *why* you're using a Dictionary? Do you really need key lookup? Or is it just being used as list of KeyValuePair? – Caius Jard Feb 16 '22 at 16:03

1 Answers1

0

(For the sake of suggesting something)

Inspired by this answer, I am going to suggest an approach where you create your own type that inherits from List<KeyValuePair<char, int>> (thereby preserving insertion order), and hides the base.Add() method using a method that ensures that only entries with unique keys are added to your list.

This works nicely for your specific use case if you can add your items one by one, and if adding items to your list is the only way you intend to change your list content. If there is a chance you may need to e.g. remove items from your list, the implementation needs to be extended to cover that.

Also worth mentioning: This implementation lets an incident of trying to add an entry with a non-unique key to the list pass silently. There's no screaming; the entry is simply not added.

Implementation:

public class UniqueKeyKvpList : List<KeyValuePair<char, int>>
{
    private readonly HashSet<char> _keys = new HashSet<char>();

    // Hiding base method List<KeyValuePair<char, int>>.Add()
    public new void Add(KeyValuePair<char, int> kvp) => Add(kvp.Key, kvp.Value);
    
    // Simpler .Add() method; imitates Dictionary.Add() in usage
    public void Add(char key, int value) => AddIfUniqueKey(key, value);

    private void AddIfUniqueKey(char key, int value)
    {
        if (!_keys.Contains(key))
        {
            _keys.Add(key);
            base.Add(new KeyValuePair<char, int>(key, value));
        }
    }
}

Usage:

var myKvpList = new UniqueKeyKvpList();

myKvpList.Add('a', 1);
myKvpList.Add('a', 2); // will not be added (duplicate key)
myKvpList.Add('b', 3);

After populating the list, you can get all chars with the max value as follows:

.Net 6 (due to use of .MaxBy())

char[] charsWithMaxValue = myKvpList
    .GroupBy(kvp => kvp.Value)
    .MaxBy(gr => gr.Key) // Group key is the kvp value
    .Select(kvp => kvp.Key)
    .ToArray();

< .Net 6

char[] charsWithMaxValue = myKvpList
    .GroupBy(kvp => kvp.Value)
    .OrderByDescending(gr => gr.Key) // Group key is the kvp value
    .First()
    .Select(kvp => kvp.Key)
    .ToArray();

The first char with the max value will then be

char firstCharWithMaxValue = charsWithMaxValue.First();

Alternatively, if computing charsWithMaxValue is not needed for anything, firstCharWithMaxValue can be calculated directly as follows:

.Net 6

char firstCharWithMaxValue = myKvpList
    .MaxBy(kvp => kvp.Value)
    .Key;

< .Net 6

char firstCharWithMaxValue = myKvpList
    .OrderByDescending(kvp => kvp.Value)
    .First()
    .Key;

Example fiddle here.

Astrid E.
  • 2,280
  • 2
  • 6
  • 17