0

I have the following Linq query:

var outputs = inputs.Select(input => input * Previous Output);

With a loop I am doing the following:

for (Int32 i = 0; i < inputs.Count(); i++)
  outputs.Add(inputs[i] * outputs[i - 1]);            

Is it possible to replicate this with Linq?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481

4 Answers4

1

I'd recommend looking into MoreLINQ's Pairwise function which does what you need here:

(Assuming an AddRange function):

outputs.AddRange(inputs.Pairwise((previous, current) => previous*current)
julealgon
  • 7,072
  • 3
  • 32
  • 77
0

is this what you want?

var outputs = inputs.Select((x, i) => x * (i == 0 ? 1 : inputs[i - 1]));
-1

With a loop I am doing the following:

Assuming that outputs is an standard C# collection (at least one of which I've used) - no, you can't cause for i = 0 i-1 will be equal to -1 which is an invalid index.

In some cases you can use Aggregate (depending on desired output with possible combination with other LINQ functions and possibly initializing previous to some other value, for example to first value of inputs ):

int[] inputs = new[] { 1, 2, 3 };
var x = inputs.Aggregate(
    (result: new List<int>(), previous: 0),
    (agg, curr) =>
    {
        agg.result.Add(curr + agg.previous);
        return (agg.result, curr);
    });
var outputs = x.result; // or outputs.AddRange(x.result)
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
-1

There are several solutions for this problem, which one is the best for you depends on whether you will use it for this problem only, or whether you want to create a reusable, testable, maintainable and easy to understand solution.

Straightforward, one time use only

You could use Enumerable.Zip to combine the elements:

Make two enumerables: the original sequence, and the same sequence containing "the next element"

IEnumerable<int> originalSequence = ...
IEnumerable<int> nextElements = oridinalSequence.Skip(1);

Multiply every nextElement with the original sequence using the overload of Enumerable.Zip that has a parameter resultSelector:

var result = nextElements.Zip(originalSequence,

// parameter resultSelector: take one nextElement and one previous to make one new:
(element, previousElement) => element * previousElement);

Note that the sequence will be enumerated twice!

Generic Solution

If you think you have to combine elements and previous elements more often to calculate a result, consider to create an extension method for it.

Apart from that this will make the code reusable, you can unit test the method, so you are certain that the code is correct and will stay correct. It will be easier to read your code, and easier to maintain.

If you are not familiar with Extension methods, see Extension Methods explained

public static IEnumerable<TResult> CombineWithPrevious<TSource, TResult>(
    IEnumerable<TSource> source,
    Func<TSource, TSource, TResult> resultSelector)
{
    // TODO implement
}

Usage will be as follows:

IEnumerable<int> inputs = ...
var outputs = inputs.CombineWithPrevious( (x, previousX) => x * previousX);

Because I created a generic resultSelector, you can use it to do anything with the [item, previousItem] combinations:

var contestants = Students.CombineWithPrevious(
    (student, previousStudent) => new Contestants
    {
         Contestant1 = student.Name,
         Contestant2 = previousStudent.Name,
    });

The nice thing with the extension method is that you can concatenate it with other LINQ methods:

var result = Students.GroupBy(student => student.ClassId)
    .Where(student => ...)
    .CombineWithPrevious(...)
    .OrderBy(...);

The implementation is straightforward. For improved readability I use small steps.

public static IEnumerable<TResult> CombineWithPrevious<TSource, TResult>(
    IEnumerable<TSource> source,
    Func<TSource, TSource, TResult> resultSelector)
{
    // TODO check valid input

    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            // There is at least one element, so we have a previousElement:
            TSource previousElement = enumerator.Current;

            // enumerate the rest of the sequence:
            while (enumerator.MoveNext())
            {
                // There are more elements, calculate the TResult:
                TResult result = resultSelector(enumerator.Current, previousElement);
                yield return result;
                previousElement = enumerator.Current;
            }
        }
    }
}

This version will enumerate your sequence at utmost once. If you only need the first few items of the result (for instance: FirstOrDefault), then no more items than needed will be enumerated.

This method will be easy to unit test.

This version will return an empty sequence if source contains zero or one element.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116