如何有效地获得最高和最高List <double?>中的最低值,然后修改它们?</double?>

时间:2010-06-15 13:33:50

标签: c# c#-3.0 performance

我必须得到双打名单的总和。如果总和> 100,我必须从最高数字递减,直到它= 100。如果总和是&lt; 100。 100,我必须增加最小数字,直到它= 100.我可以通过循环列表,将值分配给占位符变量和测试更高或更低,但我想知道是否有任何大师可以建议一个超酷&amp;有效的方法吗?下面的代码基本上概述了我想要实现的目标:

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

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

9 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

更新:Gah,我没注意到你似乎只有4个元素在列表中。这里的答案是针对一般情况的,并且对于4元素问题将是过度杀伤。循环使用。


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

你需要为C#寻找一个好的Heap实现。你可以使用我的,但它是一个更大的类库的一部分,所以它可能是一个橡胶球拉入你的项目。代码在这里:My Mercurial Repository

我将在下面展示我的实施如何运作的例子。

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

所以我会这样做:

  1. 为所有元素创建一个包含值+位置的堆,并对其进行排序,以使最高值为第一个
  2. 为所有元素创建一个包含值+位置的堆,并对其进行排序,以使最低值为第一个
  3. 然后,如果总和&lt; 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
            }
        }
    }
    

答案 2 :(得分:2)

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

我认为最有效的方法是进行索引排序,即创建一个索引数组,按照它们指向的值排序。当您开始递增或递减值时,您可能需要的不仅仅是最小或最大的数字。

答案 3 :(得分:1)

如果我正确阅读了您的代码&lt;&gt;只会有4个成员,对吧? 如果是这样,则不需要或建议循环。

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

进行拼图

答案 4 :(得分:0)

我会这样做:

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

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

答案 5 :(得分:0)

不确定我是否理解你的问题......这个怎么样?

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

答案 6 :(得分:0)

以下是使用Linq的Aggregate方法的实现(假设list是双精度数组的列表或数组,或实现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循环更清晰(恕我直言......)

答案 7 :(得分:0)

好像我最初误解了这个问题。显然,目标不是找到最高/最低,并且在该总和为100之前将+/- 1添加到该元素;目标是每次添加+/- 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;
}

答案 8 :(得分:0)

以最简单的方式获得最高......

// 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]);