Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何有效地从列表中获取最高值和最低值,然后对其进行修改?_C#_C# 3.0_Performance - Fatal编程技术网

C# 如何有效地从列表中获取最高值和最低值,然后对其进行修改?

C# 如何有效地从列表中获取最高值和最低值,然后对其进行修改?,c#,c#-3.0,performance,C#,C# 3.0,Performance,我必须得到一系列双倍的总和。如果总和大于100,我必须从最大的数字开始递减,直到它等于100。如果总和

我必须得到一系列双倍的总和。如果总和大于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个变量中,然后用if/then解决它。更新:Gah,我没有注意到列表中似乎只有4个元素。这里的答案是针对一般情况的,对于一个4元素的问题来说,答案是过分的。继续循环

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

不过,您需要为C寻找一个好的堆实现。您可以使用我的,但它是一个更大类库的一部分,因此它可能有点像一个橡皮球,不利于您的项目。代码在这里:

下面我将举例说明我的实现是如何工作的

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

所以我要做的是:

为所有元素创建一个包含value+position的堆,并进行排序,以使最高值为第一个值 为所有元素创建一个包含value+position的堆,并进行排序,以使最小的值为第一个值 然后,如果总和<0,则获取堆值+位置的第一个元素,将原始列表中的值增加1,然后将堆的第一个元素替换为值+1,位置。替换堆的第一个元素的效果是删除它,然后读取它,如果它不再是最高/最低值,则可能会将它移动到与第一个元素不同的位置。例如,假设您有以下列表:

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)
list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)
list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)
int[] values = new int[] { ... };
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);
}
假设总数仍然很低,所以你重复一遍。现在,当重新添加3,0而不是2,0时,它将被推回到堆中一点,因此看起来如下所示:

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)
list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)
list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)
int[] values = new int[] { ... };
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);
}
现在,2值是最低的,因此是堆的第一个元素。请注意,这些操作不会对整个堆进行重新排序,只对必要的部分进行重新排序。因此,堆是此类算法的理想选择,因为它们在进行修改时保持排序的成本很低

让我们看一些代码。我假设你有这样一个值数组:

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)
list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)
list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)
int[] values = new int[] { ... };
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);
}
因此,在我的堆实现中,以下内容可以满足您的需要:

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

我会这样做:

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)
list: 2, 2, 3
heap: (2, 0), (2, 1), (3, 1)
list: 3, 2, 3
heap: (2, 1), (3, 0), (3, 1)
int[] values = new int[] { ... };
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的聚合方法的实现,假设list是一个列表或Double数组,或者实现IList的任何东西:


这种技术的优点是它只对列表进行一次迭代,而且代码比使用foreach循环更清晰。…

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

根据萨尼的观点,这里有另一个答案:

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

你能把清单分类吗?或者它必须是当前的顺序吗?它必须保持与添加项目相同的顺序-我将更新问题以反映这一点,并且
如果在达到目标之前,递减最大值会使其成为第二高值,那该怎么办?当你可以对“资产分割对象值”进行计算时,为什么要使用列表使事情复杂化?如果你的总和是100.5,您将要做什么?在*logn上构建堆的时间复杂度不是很高吗?尽管堆的时间复杂度很高,但这可能是最有效的算法。老实说,这是应该被接受的答案。这取决于数组中元素的复杂性。我的解决方案在构建额外的数据结构时会有一些初始开销,但在此之后,不需要搜索、排序或循环来找到要调整的元素。当然,与所有解决方案一样,配置文件,配置文件,配置文件!不,你的解决方案肯定比公认的答案更快。接受的答案具有On*m复杂性,其中n是列表中的元素数,m是最大值。你的是*logn+Om,所以你有更好的最坏情况复杂性,但最坏情况复杂性是不是?这真的取决于哪个支配n或m。起初我以为你的回答会慢一些,但那是在我意识到我和其他大多数人对这个问题理解错误之前。这似乎做了一些奇怪的事情,比如使用RemoveAt/Insert而不是仅仅使用splitValues[idx]。就这一点而言,我认为当您可以计算正确的值来使用时,没有必要继续循环。虽然基本思想是正确的,代码可能很好地工作,但我不相信它目前的生产质量。没有做任何奇怪的事情。它取出最高/最低值,递增/递减一个值,然后将其插入列表中的同一位置。都是按照老年退休金计划的规定。当然有必要保持循环,直到命中目标,因为我假设递增/递减值为1。谁说了关于产品质量的事?OP告诉了我们他在用什么,我修正了他的密码。如果我能自己写的话,我不一定会这样写。但在这种情况下,OP为我们提供了一个起点。因此,我的好先生,你错了。虽然这是有效的,但这绝对不是一个有效的方法。因此,这不是OP所要求的。如果你看到我的和OPs对他的问题的评论,你会发现,高效并不意味着快速、小内存占用或短执行路径,而是干净、可读的代码。当然,我可以让它更具可读性,但这是您个人对代码可读性的偏好。对我来说,上面的代码是完全可读的。