简体   繁体   English

如何有效地从列表中获取最高和最低值 <double?> ,然后修改它们?

[英]How to efficiently get highest & lowest values from a List<double?>, and then modify them?

I have to get the sum of a list of doubles. 我必须得到双打列表的总和。 If the sum is > 100, I have to decrement from the highest number until it's = 100. If the sum is < 100, I have to increment the lowest number until it's = 100. I can do this by looping though the list, assigning the values to placeholder variables and testing which is higher or lower but I'm wondering if any gurus out there could suggest a super cool & efficient way to do this? 如果总和> 100,则必须从最高数字开始递减,直到它等于100。如果总和<100,我必须将最低数字递增到直到=100。我可以通过遍历列表,分配占位符变量和测试的值是较高还是较低,但是我想知道是否有专家认为这是一种超酷而高效的方法? The code below basically outlines what I'm trying to achieve: 以下代码基本上概述了我要实现的目标:

var splitValues = new List<double?>();
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0));

var listSum = splitValues.Sum(split => split.Value);
if (listSum != 100)
{
    if (listSum > 100)
    {
        // how to get highest value and decrement by 1 until listSum == 100
        // then reassign back into the splitValues list?
        var highest = // ??
    }
    else
    {
        // how to get lowest where value is > 0, and increment by 1 until listSum == 100
        // then reassign back into the splitValues list?
        var lowest = // ??
    }
}

update: the list has to remain in the same order as the items are added. 更新:列表必须保持与添加项目相同的顺序。

I think the most efficient thing is probably to not use the List.Sum() method, and do one loop that calculates the sum, lowest, and highest. 我认为最有效的方法可能是不使用List.Sum()方法,并执行一个循环来计算总和,最低和最高。 It's also easy to write, debug, read, and maintain, which I would say qualifies as super-cool. 它的编写,调试,读取和维护也很容易,我认为这很酷。

Update : Gah, I didn't notice that you seem to only have 4 elements in the list. 更新 :加,我没有注意到您似乎只在列表中包含4个元素。 The answer here is for the general case, and will be overkill for a 4-element problem. 答案是针对一般情况,对于4元素问题将是过大的杀伤力。 Go with looping. 循环播放。


Well, personally I would use a Heap data structure, where each element is the value of each element + its position in the original list. 好吧,我个人将使用Heap数据结构,其中每个元素是每个元素的值及其在原始列表中的位置。

You need to hunt down a good Heap implementation for C# though. 不过,您需要为C#寻找良好的Heap实现。 You can use mine, but it is part of a larger class library so it might be a bit of a rubber ball to pull into your project. 您可以使用我的,但它是较大的类库的一部分,因此插入您的项目可能有点麻烦。 The code is here: My Mercurial Repository . 代码在这里: 我的Mercurial存储库

I'll be showing examples below on how my implementation would work. 我将在下面显示有关我的实现如何工作的示例。

If you don't know how a heap works, it is basically a data structure that looks a bit like an array, but also a bit like a tree, where the nodes of the tree is stored in the array. 如果您不知道堆是如何工作的,则它基本上是一种数据结构,看起来有点像数组,但也有点像树,树的节点存储在数组中。 There's a bit of code involved that "intelligently" moves items around. 有一些代码可以“智能地”移动项目。 The beauty of it is that it is super easy to get the highest or lowest item (that is, one of them, not both from the same heap), since it will always be the first element in the array. 它的优点在于,获取最高或最低的项(即其中的一个,而不是从同一堆中获取的两者)非常容易,因为它始终是数组中的第一个元素。

So what I would do is this: 所以我要做的是:

  1. Create a heap containing value+position for all elements, sorted so that the highest value is the first one 创建一个包含所有元素的value + position的堆,并对其进行排序,以使最高值为第一个
  2. Create a heap containing value+position for all elements, sorted so that the lowest value is the first one 创建一个包含所有元素的value + position的堆,对堆进行排序,使最低的值为第一个

Then, if the sum < 0, grab the first element of the heap (value + position), increase the value in the original list by 1, then replace the first element of the heap with (value+1),position. 然后,如果sum <0,则获取堆的第一个元素(值+位置),将原始列表中的值增加1,然后用(value + 1),position 替换堆的第一个元素。 Replacing the first element of the heap has the effect of removing it, and then readding it, potentially moving it to a different position than the first one if it is no longer the highest/lowest value. 替换堆的第一个元素的作用是先删除它,然后读取它,如果不再是最高/最低值,则有可能将其移动到与第一个元素不同的位置。 For instance, let's say you have the following list: 例如,假设您有以下列表:

list: 1, 2, 3

The heap now looks like this: 现在,堆看起来像这样:

heap: (1, 0), (2, 1), (3, 2)  <-- second value is position, 0-based

ie. 即。 you build it up like this: 您可以像这样建立它:

position:  0, 1, 2
list:      1, 2, 3
           |  |  +-------+
           |  |          |
         +-+  +--+       |
         |       |       |
       <-+>    <-+>    <-+>
heap: (1, 0), (2, 1), (3, 2)

Now, if the sum is too low, you grab the first element of the lo-heap, which is (1, 0) , increase the value at position 0 in the original list by 1, then replace the first element of the heap (which is still (1, 0) ) with a new element containing the new value, at the same position. 现在,如果总和太低,您将获取lo-heap的第一个元素(1, 0) ,将原始列表中位置0的值增加1,然后替换堆的第一个元素(仍为(1, 0) ),并且在同一位置具有包含新值的新元素。

After the replace, the list and heap now looks like this: 替换后,列表和堆现在如下所示:

list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)

Let's say the sum is still to low, so you repeat. 假设总和仍然很低,所以您重复一下。 Now, when re-adding (3, 0) instead of (2, 0) , it will be pushed a bit back into the heap, so it looks like this: 现在,当重新添加(3, 0)而不是(2, 0) ,它将被稍微推回堆中,因此如下所示:

list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)

Now, the 2-value is now the lowest one, and thus the first element of the heap. 现在,2值现在是最低的值,因此也是堆的第一个元素。 Note that these operations does not reorder the entire heap, only the portions necessary. 请注意,这些操作不会重新排列整个堆,仅重新排列必要的部分。 As such, a heap is ideal for algorithms like this since they are cheap to keep sorted when doing modifications. 因此,堆对于此类算法是理想的,因为在进行修改时保持排序很便宜。

So let's see some code. 因此,让我们看一些代码。 I'm assuming you have an array of values like this: 我假设您有一个这样的值数组:

int[] values = new int[] { ... };

Thus, with my heap implementation, the following would do what you want: 因此,通过我的堆实现,以下将完成您想要的事情:

using System;
using System.Collections.Generic;
using System.Linq;
using LVK.DataStructures.Collections;

namespace SO3045604
{
    class LowestComparer : IComparer<Tuple<int, int>>
    {
        public int Compare(Tuple<int, int> x, Tuple<int, int> y)
        {
            return x.Item1.CompareTo(y.Item1);
        }
    }

    class HighestComparer : IComparer<Tuple<int, int>>
    {
        public int Compare(Tuple<int, int> x, Tuple<int, int> y)
        {
            return -x.Item1.CompareTo(y.Item1);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            var valuesWithPositions = values
                .Select((value, index) => Tuple.Create(value, index));

            var loHeap = new Heap<Tuple<int, int>>(
                new LowestComparer(),
                valuesWithPositions);
            var hiHeap = new Heap<Tuple<int, int>>(
                new HighestComparer(),
                valuesWithPositions);

            int sum = values.Aggregate((a, b) => a + b);

            while (sum < 75)
            {
                var lowest = loHeap[0];
                values[lowest.Item2]++;
                loHeap.ReplaceAt(0, 
                    Tuple.Create(lowest.Item1 + 1, lowest.Item2));
                sum++;
            }
            while (sum > 55)
            {
                var highest = hiHeap[0];
                values[highest.Item2]--;
                hiHeap.ReplaceAt(0,
                    Tuple.Create(highest.Item1 - 1, highest.Item2));
                sum--;
            }

            // at this point, the sum of the values in the array is now 55
            // and the items have been modified as per your algorithm
        }
    }
}

The most efficient way is to write a plain and simple loop that does the work, that will give the least amount of overhead. 最有效的方法是编写一个简单而简单的循环来完成工作,这将使开销最小。 You have to look at all values in the list to find the largest or smallest, so there is no shortcuts. 您必须查看列表中的所有值才能找到最大或最小的值,因此没有捷径。

I think that the most efficient would be to make an index sort, ie create an array of indexes that you sort by the values that they point to. 我认为最有效的方法是进行索引排序,即创建一个索引数组,然后根据它们所指向的值对其进行排序。 When you start to increment or decrement the values, you may need more than just the smallest or largest number. 当开始增加或减少值时,您可能需要的不仅仅是最小或最大的数字。

If I read that code correctly your List<> is only going to have exactly 4 members, right? 如果我正确地阅读了该代码,则您的List <>将仅具有4个成员,对吗?
If so, looping is not required or recommended. 如果是这样,则不需要或不建议循环。

Just store your data in 4 vars and puzzle it out with if/then 只需将您的数据存储在4个变量中,然后用if / then将其困惑

I'd do it something like this: 我会做这样的事情:

var splitValues = new List<double?>();
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0));
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0));

var listSum = splitValues.Sum(split => split.Value);
while (listSum != 100)
{
  var value = listSum > 100 ? splitValues.Max() : splitValues.Min();
  var idx = splitValues.IndexOf(value);
  splitValues.RemoveAt(idx);
  splitValues.Insert(idx, value + (listSum > 100 ? -1 : 1));
  listSum = splitValues.Sum(split => split.Value);
}

Note: This solution would work for any number of elements in the list. 注意:此解决方案适用于列表中任意数量的元素。

Not sure if I understood your question... How about this? 不知道我是否理解您的问题...如何?

        const double MIN_VALUE = 0.01;

        var values = new List<double>();
        var rand = new Random();
        for (int i = 0; i < 100; i++)
        {
            values.Add(rand.Next(0, 100) / 10);
        }

        double sum = 0, min = 0, max = 0;
        for (int i = 0; i < values.Count; i++)
        {
            var value = values[i];
            sum += value;
            min = Math.Min(value, min);
            max = Math.Max(value, max);
        }

        if (min == 0) min = MIN_VALUE;
        if (max == 0) max = MIN_VALUE;
        while (Math.Abs(sum - 100) > MIN_VALUE)
        {
            if (sum > 100)
                sum -= max;

            if (sum < 100)
                sum += min;
        }

Here's an implementation using Linq's Aggregate method (assuming list is a list or array of doubles, or anything that implements IList<double> ) : 这是使用Linq的Aggregate方法的实现(假设list是double的列表或数组,或者任何实现IList<double> ):

var stats = list.Aggregate(
                     StatsAggregate.Default,
                     (a, v) => a.ProcessValue(v));

if (stats.Sum > 100.0)
{
    list[stats.MaxIndex] -= (stats.Sum - 100.0);
}
else if (stats.Sum < 100.0)
{
    list[stats.MinIndex] += (100.0 - stats.Sum);
}

...

struct StatsAggregate
{
    public static StatsAggregate Default
    {
        get
        {
            return new StatsAggregate
            {
                Sum = 0,
                Min = double.MaxValue,
                MinIndex = -1,
                Max = double.MinValue,
                MaxIndex = -1
            };
        }
    }

    private int currentIndex;

    public double Sum { get; private set; }
    public double Min { get; private set; }
    public double Max { get; private set; }
    public int MinIndex { get; private set; }
    public int MaxIndex { get; private set; }

    public StatsAggregate ProcessValue(double value)
    {
        return new StatsAggregate
        {
            Sum = this.Sum + value,
            Max = Math.Max(this.Max, value),
            MaxIndex = value > this.Max ? currentIndex : MaxIndex,
            Min = Math.Min(this.Min, value),
            MinIndex = value < this.Max ? currentIndex : MinIndex,
            currentIndex = currentIndex + 1
        };
    }
}

The advantage of this technique is that it iterates the list only once, and the code is clearer than with a foreach loop (IMHO...) 这种技术的优点是只对列表进行一次迭代,并且代码比使用foreach循环(IMHO ...)更清晰。

It appears as though I misunderstood the question at first. 似乎我一开始误解了这个问题。 Apparently the goal is not to find the highest/lowest and add +/-1 to that element until the sum is 100; 显然,目标是不找到最高/最低值并将该元素加+/- 1,直到总和为100; the goal is to find the new highest/lowest every time you add +/-1. 目标是每次添加+/- 1时都会找到新的最高/最低值。

Here's another answer, based on Sani's: 这是基于Sani的另一个答案:

var splitValues = new List<double?>(); 
splitValues.Add(Math.Round(assetSplit.EquityTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.PropertyTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.FixedInterestTypeSplit() ?? 0)); 
splitValues.Add(Math.Round(assetSplit.CashTypeSplit() ?? 0)); 

var listSum = splitValues.Sum();
while (listSum != 100) 
{ 
    int increment = listSum > 100 ? -1 : 1;
    var value = listSum > 100 ? splitValues.Max() : splitValues.Min();
    splivValue[splitValues.IndexOf(value)] += increment;
    listSum += increment;
}

To get highest... in a very simple way: 为了获得最高...以一种非常简单的方式:

// For type safety
List<double> list = new List<double>()
{ 1.2, 4.3, 7.8, 4.4, 9.3, 10.2, 55.6, 442.45, 21.32, 43.21 };

d.Sort();
list.Sort();

Console.WriteLine(d[d.Count - 1].ToString());
Console.WriteLine(list[list.Count - 1]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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