簡體   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