繁体   English   中英

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

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

我必须得到双打列表的总和。 如果总和> 100,则必须从最高数字开始递减,直到它等于100。如果总和<100,我必须将最低数字递增到直到=100。我可以通过遍历列表,分配占位符变量和测试的值是较高还是较低,但是我想知道是否有专家认为这是一种超酷而高效的方法? 以下代码基本上概述了我要实现的目标:

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 = // ??
    }
}

更新:列表必须保持与添加项目相同的顺序。

我认为最有效的方法可能是不使用List.Sum()方法,并执行一个循环来计算总和,最低和最高。 它的编写,调试,读取和维护也很容易,我认为这很酷。

更新 :加,我没有注意到您似乎只在列表中包含4个元素。 答案是针对一般情况,对于4元素问题将是过大的杀伤力。 循环播放。


好吧,我个人将使用Heap数据结构,其中每个元素是每个元素的值及其在原始列表中的位置。

不过,您需要为C#寻找良好的Heap实现。 您可以使用我的,但它是较大的类库的一部分,因此插入您的项目可能有点麻烦。 代码在这里: 我的Mercurial存储库

我将在下面显示有关我的实现如何工作的示例。

如果您不知道堆是如何工作的,则它基本上是一种数据结构,看起来有点像数组,但也有点像树,树的节点存储在数组中。 有一些代码可以“智能地”移动项目。 它的优点在于,获取最高或最低的项(即其中的一个,而不是从同一堆中获取的两者)非常容易,因为它始终是数组中的第一个元素。

所以我要做的是:

  1. 创建一个包含所有元素的value + position的堆,并对其进行排序,以使最高值为第一个
  2. 创建一个包含所有元素的value + position的堆,对堆进行排序,使最低的值为第一个

然后,如果sum <0,则获取堆的第一个元素(值+位置),将原始列表中的值增加1,然后用(value + 1),position 替换堆的第一个元素。 替换堆的第一个元素的作用是先删除它,然后读取它,如果不再是最高/最低值,则有可能将其移动到与第一个元素不同的位置。 例如,假设您有以下列表:

list: 1, 2, 3

现在,堆看起来像这样:

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

即。 您可以像这样建立它:

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

现在,如果总和太低,您将获取lo-heap的第一个元素(1, 0) ,将原始列表中位置0的值增加1,然后替换堆的第一个元素(仍为(1, 0) ),并且在同一位置具有包含新值的新元素。

替换后,列表和堆现在如下所示:

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

假设总和仍然很低,所以您重复一下。 现在,当重新添加(3, 0)而不是(2, 0) ,它将被稍微推回堆中,因此如下所示:

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

现在,2值现在是最低的值,因此也是堆的第一个元素。 请注意,这些操作不会重新排列整个堆,仅重新排列必要的部分。 因此,堆对于此类算法是理想的,因为在进行修改时保持排序很便宜。

因此,让我们看一些代码。 我假设您有一个这样的值数组:

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

因此,通过我的堆实现,以下将完成您想要的事情:

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
        }
    }
}

最有效的方法是编写一个简单而简单的循环来完成工作,这将使开销最小。 您必须查看列表中的所有值才能找到最大或最小的值,因此没有捷径。

我认为最有效的方法是进行索引排序,即创建一个索引数组,然后根据它们所指向的值对其进行排序。 当开始增加或减少值时,您可能需要的不仅仅是最小或最大的数字。

如果我正确地阅读了该代码,则您的List <>将仅具有4个成员,对吗?
如果是这样,则不需要或不建议循环。

只需将您的数据存储在4个变量中,然后用if / then将其困惑

我会做这样的事情:

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);
}

注意:此解决方案适用于列表中任意数量的元素。

不知道我是否理解您的问题...如何?

        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;
        }

这是使用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
        };
    }
}

这种技术的优点是只对列表进行一次迭代,并且代码比使用foreach循环(IMHO ...)更清晰。

似乎我一开始误解了这个问题。 显然,目标是不找到最高/最低值并将该元素加+/- 1,直到总和为100; 目标是每次添加+/- 1时都会找到新的最高/最低值。

这是基于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;
}

为了获得最高...以一种非常简单的方式:

// 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