我如何在字典中获取前N个(百分比)值?

时间:2014-03-03 03:23:08

标签: c# linq

我有一个包含字符串键和整数值的字典。该值表示密钥的出现次数。

如何使用代表前25%值的键和值创建新词典?值的总和应等于或大于所有值的总和。例如,如果我的字典包含5个具有值(5,3,2,1,1)的项目,并且我想要前50%,则新字典将包含值(5,3),因为它们的总和是8,那是&gt ; = 12%的50%。这个词典需要按值降序排序,然后采用前N个,使得它们的总和符合指定的百分比。

此代码为我提供了前N名,但基于已知计数。如何考虑所需的百分比?

var topItemsCount = dictionary.OrderByDescending(entry => entry.Value)
                   .Take(topN)
                   .ToDictionary(pair => pair.Key, pair => pair.Value);

3 个答案:

答案 0 :(得分:1)

类似的东西:

var topItemsCount = dictionary.OrderByDescending(entry => entry.Value)
               .Take(Math.Floor(dictionary.Count * 0.25))
               .ToDictionary(pair => pair.Key, pair => pair.Value);

在字典上运行.Count会返回集合中键值对的数量。将Math.Floor舍入到最接近的int。

编辑以反映评论

我可能只是使用一个简单的非linq解决方案来实现你想要的。也许更冗长,但任何人都清楚它的作用:

var total = dictionary.Sum(e => e.Value);
var cutoff = total * 0.5;
var sum = 0;

var pairs = new List<KeyValuePair<string, int>>();
foreach (var pair in dictionary.OrderByDescending(e => e.Value))
{
     sum += pair.Value;
     pairs.Add(pair);

     if (sum > cutoff)
         break;
}

dictionary = pairs.ToDictionary(pair => pair.Key, pair => pair.Value);

再修改一次

如果你真的想要更多的linq,你可以试着拿着累积的班级变量。

private static int sum = 0;

static void Main(string[] args)
{
    var dictionary = new Dictionary<string, int>()
    {
        {"1",5},         
        {"2",3},
        {"3",2},
        {"4",1},
        {"5",1},
    };

    var total = dictionary.Sum(e => e.Value);
    var cutoff = total * 0.5;

    var filtered = dictionary.OrderByDescending(e => e.Value)
        .TakeWhile(e => Add(e.Value).Item1 < cutoff)
        .ToDictionary(pair => pair.Key, pair => pair.Value);
}

private static Tuple<int, int> Add(int x)
{
    return Tuple.Create(sum, sum += x);
}

使用返回元组的add函数有点令人费解,因为你在结果中包含了第一个违反截止值的值(即使5 + 3 = 8大于截止6,你仍然包括3 )。

答案 1 :(得分:1)

将问题改为两部分:

  1. 给定字符串和值列表,找到代表第N个百分比的值
  2. 给定一个字符串和值列表,以及一个表示第N个百分比的值,返回一个新的字符串列表和值大于或等于给定数字的值。
  3. 问题1看起来像

    double percent = inputValue;
    double n = dictionary.Values.Sum() * percent;
    

    问题2看起来像:

    Dictionary<string, int> newValues = dictionary.OrderByDescending(_ => _.Value)
        .Aggregate(
            new {sum = 0.0, values = new Dictionary<string, int>()},
            (sumValues, kv) =>
            {
                if (sumValues.sum <= n)
                    sumValues.values.Add(kv.Key, kv.Value);
                return new {sum = sumValues.sum + kv.Value, values = sumValues.values};
            },
            sumValues => sumValues.values);
    

    您也可以使用for循环和运行总和,但是对于运行范围有限的总计,我喜欢Aggregate函数的紧凑性。这样做的缺点是整个源字典仍然是迭代的。自定义迭代器方法可以解决这个问题。例如:

    public static class Extensions
    {
        public static IEnumerable<TThis> TakeGreaterThan<TThis>(this IEnumerable<TThis> source, Func<TThis, double> valueFunc, double compareTo)
        {
            double sum = 0.0;
            IEnumerable<TThis> orderedSource = source.OrderByDescending(valueFunc);
            var enumerator = orderedSource.GetEnumerator();
            while (sum <= compareTo && enumerator.MoveNext())
            {
                yield return enumerator.Current;
                sum += valueFunc(enumerator.Current);
            }
        }
    }
    

    用作

    Dictionary<string, int> newValues = dictionary.TakeGreaterThan(_ => _.Value, n).ToDictionary(_ => _.Key, _ => _.Value);
    

答案 2 :(得分:0)

可能是这个吗?

var dictionary = new Dictionary<string, int>()
{
    {"1",5},         
    {"2",3},
    {"3",2},
    {"4",1},
    {"5",1},
};

var max = dictionary.Values.Max();
int percent = 50;
int percentageValue = max*percent /100;

var topItems = dictionary.OrderByDescending(entry => entry.Value)
       .TakeWhile(x => x.Value > percentageValue)
       .ToDictionary(pair => pair.Key, pair => pair.Value);

foreach (var item in topItems)
{
    Console.WriteLine(item.Value);
}

输出:

 5
 3