[英]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.