[英]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存儲庫 。
我將在下面顯示有關我的實現如何工作的示例。
如果您不知道堆是如何工作的,則它基本上是一種數據結構,看起來有點像數組,但也有點像樹,樹的節點存儲在數組中。 有一些代碼可以“智能地”移動項目。 它的優點在於,獲取最高或最低的項(即其中的一個,而不是從同一堆中獲取的兩者)非常容易,因為它始終是數組中的第一個元素。
所以我要做的是:
然后,如果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.