【问题标题】:Finding nearest sumElement combination in list在列表中查找最近的 sumElement 组合
【发布时间】:2016-09-30 07:40:48
【问题描述】:

我在查找列表中最近的 sumElement 组合时遇到了一些问题。

例子:

这是我的清单:

 list = {32183,15883,26917,25459,22757,25236,1657}
 list.Sum = 150092

现在我要分开了

 list.Sum / z
 z = variable(user Input - in this example it's 3)

我得到了

50031

现在我想从 listElement summs 中找到最接近的数字。

最接近50031的是

 32183 + 15883 = 48066
       or
 32183 + 15883 + 26917 = 74983

所以我选择 48066,接下来我想找到下一个元素,但我们必须跳过已经计数的元素(在这种情况下我必须跳过 32183 + 15883)

所以现在我们只能使用这些元素 26917,25459,22757,25236,1657(尚未计算)

  26917 + 25459 = 52376
        or
  26917 + 25459 + 22757 = 75133

所以我选择52376

我们这样做 z(variable) 次

我们可以按这个顺序对元素求和,例如我们不能相加

 32183 + 15883 + 1657

因为这个跳过了几个列表元素

我们可以对这种排序中的元素求和,但不能对列表进行排序。 我们不能这样做,因为这些数字是 .csv 文件中的行数,所以我必须按此顺序进行。

现在我有:

for (int i = 0; i < z; i++)
{
    mid = suma/z ;

    najbliższy = listSum.Aggregate((x, y) => Math.Abs(x - mid) < Math.Abs(y - mid) ? x : y);
}

它找到我第一个元素(正确),但我不知道如何正确循环它。所以我只有第一个元素,在这个例子中我需要 3 个。

谁能帮我完成这个?

【问题讨论】:

  • Closest number 表示接近另一个的单个数。然而,您的代码试图找到最接近另一个的数字组合。请编辑您的标题和文字以说明您想要什么
  • 你加在一起的数字在列表中总是相邻吗?还是可以有差距?
  • do list 有 n 项还是只有 7 项?
  • 听起来像是Knapsack problem的变体
  • 重要:列表可以包含负数吗?如果没有,则有明显的优化。

标签: c# .net algorithm


【解决方案1】:

以下代码的输出是:

Target = 50031

32183 15883 Total: 48066
26917 25459 Total: 52376
22757 25236 1657 Total: 49650

您只需调用 FindSubsetsForTotal() 即可接收所有子集的序列,您可以对其进行迭代。

代码:

using System;
using System.Collections.Generic;

namespace Demo
{
    public class Program
    {
        static void Main()
        {
            var numbers = new[] {32183, 15883, 26917, 25459, 22757, 25236, 1657};
            int target = 50031;

            foreach (var subset in FindSubsetsForTotal(numbers, target))
            {
                int subtotal = 0;

                for (int i = subset.Item1; i <= subset.Item2; ++i)
                {
                    Console.Write(numbers[i] + " ");
                    subtotal += numbers[i];
                }

                Console.WriteLine("Total: " + subtotal);
            }
        }

        public static IEnumerable<Tuple<int, int>> FindSubsetsForTotal(IList<int> numbers, int target)
        {
            int i = 0;

            while (i < numbers.Count)
            {
                int end = endIndexOfNearestSum(numbers, i, target);
                yield return new Tuple<int, int>(i, end); // The subset is from i..end inclusive. Return it.
                i = end + 1;                              // On to the next subset.
            }
        }

        static int endIndexOfNearestSum(IList<int> numbers, int start, int target)
        {
            int sumSoFar    = 0;
            int previousSum = 0;

            for (int i = start; i < numbers.Count; ++i)
            {
                sumSoFar += numbers[i];

                if (sumSoFar > target)
                {
                    if (Math.Abs(sumSoFar - target) < Math.Abs(previousSum - target))
                        return i;

                    return i - 1;
                }

                previousSum = sumSoFar;
            }

            return numbers.Count - 1;
        }
    }
}

【讨论】:

    【解决方案2】:

    我编写的代码似乎符合您的描述。这个想法是保留一个bin,代码将在其中添加连续的数字。

    连续,因为你说如果我们不能添加,我们就不能添加

    跳过情侣列表元素

    现在,当决定添加到bin 时,如果bin 的总和小于目标值,它将始终尝试这样做。并且仅当添加新值使总数更接近目标值时才会添加。如果不满足这些条件,则不会将该号码添加到bin

    因此,如果代码决定不向bin 添加数字,那么它将创建一个新的bin。现在,始终存储迄今为止最好的bin,一旦使用bin 完成代码,它会将其与那个进行比较,如果更好,则替换它,如果它不只是丢弃当前的@ 987654330@ 重新开始。

    这些是我的参数:

    var list = new List<int>{32183,15883,26917,25459,22757,25236,1657};
    var sum = list.Sum();
    var z = 3; // user input
    var mid = (int)Math.Ceiling(sum / (double)z); // cutout point
    

    注意:我使用 Ceiling 进行舍入,因为 sum (150092) 除以 3 是 50030.666666...

    var bin = new List<int>();
    var binTotal = 0;
    var bestBin = bin;
    var bestBinTotal = binTotal;
    var candidatesCount = 0;
    
    for(var index = 0; index < list.Count; index++)
    {
        var current = list[index];
        var keep =
            (
                // The total of the bin is yet to reach the cutout point
                binTotal < mid
                // And adding the current will make it clouser
                && Math.Abs(mid - (binTotal + current)) < Math.Abs(mid - binTotal)
            )
            // but if this is the last candidate, screw that and add anyway
            || candidatesCount == (z - 1);
        if (keep)
        {
            bin.Add(current);
            binTotal += current;
        }
        else
        {
            candidatesCount++;
            if (Math.Abs(mid - binTotal) < Math.Abs(mid - bestBinTotal))
            {
                bestBin = bin;
                bestBinTotal = binTotal;
            }
            bin = new List<int>{current}; // because we didn't add it
            binTotal = current;
        }
    }
    
    Console.WriteLine("Result: {"+ string.Join(", ", bestBin) +"}; Total: " + bestBinTotal);
    

    输出是Result: {32183, 15883}; Total: 48066

    我们可以看到4806650031的距离是1965,而5003152376的距离是2345。所以代码正确判断48066更接近。

    注意:在 LinqPad 上测试。


    事实上,这些 bin 仅用于存储选定的值,因此如果您不需要,可以将其删除。相反,如果您想要的是所有候选人,您可以修改代码如下:

    var candidates = new List<int>();
    var binTotal = 0;
    var bestBinTotal = binTotal;
    
    for(var index = 0; index < list.Count; index++)
    {
        var current = list[index];
        var keep =
            (
                // The total of the bin is yet to reach the cutout point
                binTotal < mid
                // And adding the current will make it clouser
                && Math.Abs(mid - (binTotal + current)) < Math.Abs(mid - binTotal)
            )
            // but if this is the last candidate, screw that and add anyway
            || candidates.Count == (z - 1);
        if (keep)
        {
            binTotal += current;
        }
        else
        {
            candidates.Add(binTotal);
            if (Math.Abs(mid - binTotal) < Math.Abs(mid - bestBinTotal))
            {
                bestBinTotal = binTotal;
            }
            binTotal = current; // because we didn't add it
        }
    }
    
    // Fix to add the final candidate:
    
    candidates.Add(binTotal);
    
    Console.WriteLine("Result: {"+ string.Join(", ", candidates) +"}; Best: " + bestBinTotal);
    

    输出为Result: {48066, 52376, 49650}; Best: 48066

    【讨论】:

    • 我可以联系你吗?
    • @Pro100 告诉我主题。我不确定你是否想就这个问题谈论一些话题。如果它是题外话,我不知道它是公共的还是私人的。编辑:只要我们保持话题,这完全没问题。
    • @Thearot 这是关于这个话题的,但我不想在这里乱七八糟,所以我希望我们可以开始聊天,这样人们仍然可以看到它。
    • @Pro100 好的,我为这个问题创建了一个chat room
    • @Thearot 请检查聊天
    【解决方案3】:

    解决方案可能是:

      class Program
      {
        static IEnumerable<int> EnumNearestSums(IList<int> list, int z)
        {
          var target = (int)(list.Sum() / (double)z + 0.5);
          var index = 0;
    
          for (int i = 0; i < z; i++)
          {
            var sum = 0;
            for (int j = index; j < list.Count; j++)
            {
              index++;
              var tmp = sum + list[j];
              if (tmp > target)
              {
                if (Math.Abs(target - sum) < Math.Abs(target - tmp))
                {
                  index--;
                }
                else
                {
                  sum = tmp;
                }
                break;
              }
              else
              {
                sum = tmp;
              }
            }
            yield return sum;
          }
        }
    
        static void Main(string[] args)
        {
          var list = new[] { 32183, 15883, 26917, 25459, 22757, 25236, 1657 };
          var z = 3;
    
          foreach (var num in EnumNearestSums(list, z))
          {
            Console.WriteLine(num);
          }
    
          Console.ReadLine();
        }
      }
    

    结果: 48066 52376 49650

    【讨论】:

      【解决方案4】:

      嗯,这是我的第二次尝试。如果你想要随之而来的最接近的总和,即对于

      list   = {32183, 15883, 26917, 25459, 22757, 25236, 1657 ...
      target = 50031
      answer = {48066, 52376, 49650, ...  
      

      您可以尝试将items 与target 相加,然后决定是否采用 item(并返回大于target 的值)或离开item(并返回比target更小的值)

      private static IEnumerable<int> Approximations(IEnumerable<int> values, int target) {
        int sum = 0;
        bool first = true; // we have to take at least one item
      
        foreach (var item in values) {
          if (sum + item < target || first) {
            first = false;
      
            sum += item;
          }
          else {
            if (sum + item - target < target - sum) {
              yield return sum + item; // better to take the item
      
              sum = 0;
              first = true;
            }
            else {
              yield return sum; // better to leave the item
      
              sum = item;
            }
          }
        }
      
        if (first) // nothing has been taken
          yield break; 
      
        yield return sum;
      }
      

      测试

       List<int> list = new List<int>() { 32183, 15883, 26917, 25459, 22757, 25236, 1657 };
      
       int z = 3;
       int target = list.Sum() / z; // 50031
      
       // 48066, 52376, 49650
       string answer = string.Join(", ", Approximations(list, target));
      

      请注意,如果是 file,您无需阅读 整个 文件(如果 target 不依赖于文件包含):

       var solution = Approximations(File
         .ReadLines(@"C:\MyFile.txt")
         .Select(line => int.Parse(line)),
         50031);
      

      【讨论】:

      • 我想你理解错了。据我了解,一个有效的解决方案还包括32183 + 26917,在两者之间跳过一些索引。
      • @grek40:我明白了;我已经完全重建了解决方案
      • @grek40 OP 明确表示您不能跳过元素(引用:for exmaple we can't add ... Because this skip couple list elements
      • @MatthewWatson 好的,但这在我写评论的同时被编辑到问题中;)。
      猜你喜欢
      • 2015-07-26
      • 1970-01-01
      • 2021-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-25
      • 2021-11-20
      • 1970-01-01
      相关资源
      最近更新 更多