0

I have the following list:

List<Decimal?> values = new List<Decimal?> { 3, null, 2, 1, 4, 3 };

I need to create a new list that contains for each item the Maximum between the item and the two previous items so:

List<Decimal?> maximums = new List<Decimal?> { 3, 2, 4, 4 };

3 = Maximum of { 3, null, 2 }

2 = Maximum of { null, 2, 1 }

4 = Maximum of { 2, 1, 4 }

4 = Maximum of { 1, 4, 3 }

My idea is to have a loop and in each iteration do:

for (int i = 2; i < values.Count(); i++) {
  Decimal? maximum = values.Skip(i).Take(3).Max();
  maximums.Add(maximum);
} 

Is there a way to do this using only Linq?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • Just use `Select()` instead of a `for` loop – marsze May 12 '21 at 22:01
  • You are using LINQ with `Skip` and `Take`, but that is terribly inefficient when you already have a `List` and an index. Also, where is `index` set in your code? What does it have to do with the loop? If you meant `i`, consider what happens on the first iteration and why it is wrong. – NetMage May 12 '21 at 22:17
  • Is speed your objective? Something else? – mjwills May 12 '21 at 22:27
  • @NetMage What do you mean? What would you do to make more efficient? I am using take because in my example I am only getting the maximum of 3 but I might need the maximum of 10. Btw, index is only used in the for loop – Miguel Moura May 12 '21 at 23:16
  • @PeterDuniho I mean more efficient / faster. Thank you – Miguel Moura May 12 '21 at 23:17
  • @mjwills speed would be the main objective – Miguel Moura May 12 '21 at 23:17
  • 1
    Then I doubt you want LINQ. Do it with a basic for loop, with an array to store the most recent three entries (which you populate using a remainder calc (i.e. keep reusing the same array)). Also store the largest from the last three in a a separate int?. Every time you go to the next entry, check whether the array entry you are about to overwrite is the largest. If it isn't, then the max is unchanged. If it is, calc the max again. Lather, rinse, repeat. _As always - profile. I could be completely wrong._ – mjwills May 12 '21 at 23:20
  • your `for` loop has `i` not `index`. Consider, the first time through you call `Skip(2)` and then process 3. The second time through you call `Skip(3)` and then process 3. Each call to `Skip` loops around calling `MoveNext` to throw away those returned values from the list, so you are repeatedly throwing away the same values. Calling `Skip` in a loop means your code is O(n!). – NetMage May 13 '21 at 00:18

2 Answers2

4

I'm not sure why you and the other answers do a Skip(2) first. Based on your desired output of 3,2,4,4 for the input of 3,null,2,1,4,3 you will not get the correct result. If you really do want to skip 2, then modify the code below.

What you are looking for is commonly thought of as a sliding window.

If you are ok with it, there is a Morelinq method called, appropriately, Window() that will produce a sequence of sequences, each of the desired length.

You can apply this to your problem like so

var values = new List<decimal?> { 3, null, 2, 1, 4, 3 };
var maximums = values.Window(3).Select(x => x.Max()).ToList();

For each subsequence of length up to 3 we select the maximum value. This produces your desired result: 3,2,4,4.

The Window() method buffers up each sequence, but overall streams the results. There is no use of Skip() so you don't fall into the trap of scanning the source up to each index, which makes this efficient.

If you don't want to download the package, you can roll your own, and instead of producing windowed sequences, directly produce maximum values from the buffered sequences.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • 2
    My use of Skip(2) produces the correct result because id finds the max of the 3 values up to the "current" index. So it's looking "backwards" 3 spots starting with the third element. I grant you it rescans elements, though. – D Stanley May 13 '21 at 13:50
0

Sure - just use Skip(2) to emulate your loop:

values.Skip(2).Select((x,i) => values.Skip(i).Take(3).Max())
D Stanley
  • 149,601
  • 11
  • 178
  • 240