简体   繁体   中英

Query values from a C# Generic Dictionary using LINQ

Here's the code i have:

Dictionary<double, long> dictionary = new Dictionary<double, long>();
dictionary.Add(99, 500);
dictionary.Add(98, 500);
dictionary.Add(101, 8000);
dictionary.Add(103, 6000);
dictionary.Add(104, 5);
dictionary.Add(105, 2000);

double price = 100;

the query i want is: the key that is nearest price AND with the lowest value. so in the above example it should return 99. how do i code this in LINQ ? i have seen alot of linq examples but i cannt adapt any of them to my needs b/c my query has 2 conditions.

thanks for any help.

edit: based on comments from @nintendojunkie and @DmitryMartovoi i have had to rethink my approach. if i prioritize key closest to price then resulting value may not be the lowest and if i prioritize value first then the key may be too far from price so the query will have to prioritize BOTH the key and value the same and give me the lowest value with the closest key to price. both key and value are equally important. can anyone help on this? thanks

您可以这样操作:

var result = dictionary.Select(c => new { c.Key, Diff = Math.Abs(price - c.Key) + Math.Abs(price - c.Value), c.Value }).OrderBy(c => c.Diff).FirstOrDefault();

Don't forget - you use dictionary. Dictionary has only unique keys. I think you consider this structure as List<KeyValuePair<double, long>> . If so - please look to this example:

var minimumKeyDifference = dictionary.Min(y => Math.Abs(y.Key - price));
var minimumItems = dictionary.Where(x => Math.Abs(x.Key - price).Equals(minimumKeyDifference));
var desiredKey = dictionary.First(x => x.Value.Equals(minimumItems.Where(y =>  y.Key.Equals(x.Key)).Min(y => y.Value))).Key;

You say that you need to find the closest price and the lowest value, but you don't define the rules for attributing precedence between two. In the below, I'm attributing them equal precedence: a price distance of 1 is equivalent to a value of 1.

var closest = 
    dictionary.OrderBy(kvp => Math.Abs(kvp.Key - price) + kvp.Value).First();

The OrderBy(…).First() should be replaced by a MinBy(…) operator, if available, for performance.

Edit : If the value is only meant to serve as a tiebreaker, then use this (also posted by Giorgi Nakeuri ):

var closest = 
    dictionary.OrderBy(kvp => Math.Abs(kvp.Key - price))
              .ThenBy(kvp => kvp.Value)
              .First();
var price = 100.0;

var nearestKey = (from pair in dictionary
                 let diff = Math.Abs(pair.Key - price)
                 select new {Key = pair.Key, Diff = diff}
                 order by diff desc).First().Key;
var minValue = dictionary[nearestKey];

Maybe you want a magic linq query but i suggest to try the in below.

public static class MyExtensions
{
    public static double? GetNearestValue (this IDictionary<double, long> dictionary, double value)
    {
        if (dictionary == null || dictionary.Count == 0)
            return null;

        double? nearestDiffValue = null;
        double? nearestValue = null;

        foreach (var item in dictionary) {
            double currentDiff = Math.Abs (item.Key - value);
            if (nearestDiffValue == null || currentDiff < nearestDiffValue.Value) {
                nearestDiffValue = currentDiff;
                nearestValue = item.Value;
            }
        }

        return nearestValue;
    }
}

And call like this

Console.WriteLine (dictionary.GetNearestValue (100d));
var min = dictionary
            .OrderBy(pair => pair.Value)
            .Select(pair =>
                new
                {
                    k = pair.Key,
                    d = Math.Abs(pair.Key - price)
                })
            .OrderBy(t => t.d)
            .Select(t => t.k)
            .FirstOrDefault();

The following works if you change your dictionary key's data type to decimal instead of double .

decimal price = 100;
decimal smallestDiff = dictionary.Keys.Min(n => Math.Abs(n - price));
var nearest = dictionary.Where(n => Math.Abs(n.Key - price) == smallestDiff)
                        .OrderBy(n => n.Value).First();

If you use double this may fail due to rounding issues, but decimal is preferred for anything having to do with money to avoid those issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM