使用 Linq 连续总和大于 0 的连续元素

Posted

技术标签:

【中文标题】使用 Linq 连续总和大于 0 的连续元素【英文标题】:Consecutive elements with consecutive sum greater than 0 using Linq 【发布时间】:2021-08-31 09:47:34 【问题描述】:

好的,这只是我几天前解决的一个有趣问题的一小部分。想象一下,我们有一个包含 N 个元素的数组。该数组仅由 1 和 -1 组成。它看起来像这样:

int[] arr = new int[]  -1, 1, -1, 1, 1, -1 ;

我们的任务是找到保持(假想)计数器 >= 0 的连续元素的最大数量(从左到右),然后对数组进行切割,以便只剩下这些元素。

在我们的例子中,我们从计数器 0 开始:

计数器 = 0

第一个元素:-1 -> Counter = -1(低于0,所以我们重置了计数器)

秒:1 -> 计数器 = 1

第三个:-1 -> Counter = 0

第四个:1 -> Counter = 1

等等。

在上述例子中,答案是:

Solve(new int[]  -1, 1, -1, 1, 1, -1 ); // => new int[]  1, -1, 1, 1, -1 ;

现在,我的做法是这样的:

public static List<int> Solve(int[] input)

   List<List<int>> compare = new List<List<int>>();
   List<int> temp = new List<int>();
   for(int i = 0, c = 0; i < input.Length; i++)
   
       if(input[i] == 1)
       
           c++;
           temp.Add(input[i]);
       
       else if(input[i] == -1)
       
           c--;
           if (c >= 0)
               temp.Add(input[i]);
           else
           
              c = 0;
              compare.Add(temp);
              temp = new List<int>();
           
       
   

   compare.Add(temp);
   return compare.OrderByDescending(x => x.Count).First();

更多测试用例:

Solve(new int[]  -1, 1, -1, 1, 1, -1 ); // => new int[]  1, -1, 1, 1, -1 ;

Solve(new int[]  1, 1, -1, -1, 1, 1 ); // => new int[]  1, 1, -1, -1, 1, 1 ;

Solve(new int[]  1, -1, 1, -1, 1, -1, -1, 1, 1, 1 ); // => new int[]  1, -1, 1, -1, 1, -1 ;

Solve(new int[]  1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1  
// => new int[]  1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 ;

Solve(new int[]  -1, -1, -1 ); // => new int[]  ;

但我对你们所有 Linq 策划者的问题是:有没有什么方法可以在 1 行代码中使用我们心爱的 Linq 来做到这一点?

那将不胜感激:D

【问题讨论】:

对于“从右到左”的事情非常抱歉,我可能让你们中的一些人感到困惑。相应地编辑了问题。 "consecutive elements..." 在这一点上,我已经知道没有简单的 LINQ 单行。如果您想知道上一个/下一个项目是什么,Linq 并不是真正合适的工具,那是普通 for 循环的域。 @TimSchmelter 啊,真可惜,我希望有办法做到这一点。很公平,不过感谢您指出!也许其他人有其他想法?也许是一些晦涩的语言功能? 【参考方案1】:

如果您必须使用 Linq,并且您可以作弊并使用 MoreLinq,那么这种丑陋的混乱会起作用(它会返回多个集合,因为理论上您可以多次运行相同的最大大小)

Enumerable.Range(0, input.Count())
    .Select(i =>
        input.Skip(i)
             .Scan((Current: 0, Total: 0), (x, y) => (y, x.Total + y))
             .Skip(1)
             .TakeWhile(x => x.Total >= 0))
    .MaxBy(x => x.Count())
    .Select(x => x.Select(y => y.Current));

【讨论】:

确实是 Linq 的主谋。非常感谢,这个烂摊子正是我所需要的!【参考方案2】:

在这种特殊情况下,由于条件逻辑相对复杂,我会说使用迭代方法是更好的选择,但一些类似的任务可以通过使用 Aggregate 函数来解决,该函数在序列上应用累加器函数并允许您传递状态步骤之间。对于您的情况,它可能看起来像这样(使用一些 C# 9 features):

var result = arr
    .Select((i, index) => (i, index))
    .Aggregate(
        (Sum: 0, Ranges: new List<List<int>> new()), // state holder 
        (agg, curr) =>
        
            var sum = curr.i + agg.Sum;
            if (sum >= 0) agg.Ranges.Last().Add(curr.i);
            if (sum < 0)
            
                sum = 0;
                agg.Ranges.Add(new());
            

            return (sum, agg.Ranges);
        )
    .Ranges
    .Aggregate((agg, curr) => curr.Count > agg.Count ? curr : agg); // MaxBy via Aggregate

【讨论】:

似乎没有返回最长的连续整数列表,其运行总和大于或等于 0。 这似乎没有通过测试:/ 不过,感谢您的努力和解释! @Plus 抱歉,有点误解了条件。在这种情况下,使用Aggregate 不是一个好的选择,但有些地方非常适合。 @Plus 更新了答案以匹配条件。【参考方案3】:

我同意其他建议 LINQ 可能不是最佳方法的答案。这是我的看法。此版本仅将较短的序列替换为较长的序列以避免一些内存开销,然后也可以跳过 OrderByDescending 步骤。

public static List<int> Solve(int[] input)

    var temp = new List<int>();
    var longestSequence = new List<int>();

    for (int i = 0, c = 0; i < input.Length; i++)
    
        c += input[i];

        if (c >= 0)
            temp.Add(input[i]);
        else
        
            c = 0;
            if (temp.Count > longestSequence.Count)
                longestSequence = temp;
            temp = new List<int>();
        
    

    return longestSequence;

如果您真的关心内存分配,例如复制到临时列表和隐藏的列表大小调整操作,那么您可以只跟踪序列的开始和结束,然后循环返回子序列。

【讨论】:

【参考方案4】:

我最近在申请工作时必须进行的技能测试中遇到了这个问题。您需要很少的代码来解决它,并且不需要 LINQ。

int currentLevel = 0;
List<int> lst = new List<int>();
for(int x = 0; x < input.length; x++) 
  currentLevel += input[x];
  if (currentLevel >= 0) 
    lst.Add(input[x]);
  

return lst.ToArray();

【讨论】:

以上是关于使用 Linq 连续总和大于 0 的连续元素的主要内容,如果未能解决你的问题,请参考以下文章

列表中连续对的总和,包括最后一个元素与第一个元素的总和

数组中的最大总和,使得可以在 5 个元素中选择最多 2 个连续的元素

获取连续值的总和

最大连续子数组(元素数量最多)

查找环形数组的和最大的连续子数组

从链表中删除总和值为0的连续节点