-4

I'm looking for ways to calculate difference two items in array or list using LINQ.
I think ExtensionMethod is another good solution, but I wolder how to do it by LINQ.
Please Help Me.

var items = new List<int> { 238, 348, 274, 490, 459, 349 };

Console.WriteLine($"{items[0]} - 0 = {items[0]}"); 

for (int i = 1; i < items.Count; i++)
{
    Console.WriteLine($"{items[i]} - {items[i-1]} = {items[i] - items[i-1]}");
}

result:
238 - 0 = 238
348 - 238 = 110
274 - 348 = -74
490 - 274 = 216
459 - 490 = -31
349 - 459 = -110

I expect like this:

//this code does not work!!!
items.ForEach((prev, next) => Console.WriteLine($"{next} - {prev} = {next - prev}"));

Thank you.

  • You can write your extension method : https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods – vernou Sep 24 '20 at 09:44
  • Thank you for your comment.But I want to know it by LINQ. – JaeChul Kim Sep 24 '20 at 09:50
  • You could use `Aggregate()` https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.aggregate?view=netcore-3.1 or this might help: https://stackoverflow.com/questions/4460106/how-do-access-previous-item-in-list-using-linq – Christoph Lütjen Sep 24 '20 at 09:50
  • @JaeChulKim are the values unique? – Gabriel Llorico Sep 24 '20 at 09:55
  • your first line 238 - 0 has no sense, or no logic..your program cant give the result you display – Frenchy Sep 24 '20 at 09:58
  • @JaeChulKim The first line of the result with the code shown is *not* "238 - 0 = 238". – Andrew Morton Sep 24 '20 at 10:00
  • No, it is just a list. – JaeChul Kim Sep 24 '20 at 10:02
  • @ Frenchy, @Andrew Morton Please ignore first result. I just copy & paste result of my program, My mistake, sorry. – JaeChul Kim Sep 24 '20 at 10:07
  • @Christoph Lütjen Thank you for your advice. I tried and solve this program by your help. `int last = 0; var r = items.Aggregate(last, (prev,next) => { Console.WriteLine($"{next} - {prev} = {next - prev}"); return next; }, next => last); ` – JaeChul Kim Sep 24 '20 at 10:17

3 Answers3

2

Not sure why you want that logic, but one way to do it in LINQ is using Zip like below:

var items = new List<int> { 238, 348, 274, 490, 459, 349 };
var results = items.Prepend(0)
     .Zip(items, 
            (itemFromPrepended, itemFromOriginal) => 
            $"{itemFromOriginal} - {itemFromPrepended} = {itemFromOriginal - itemFromPrepended}");

items.Prepend(0) returns you a list with 0 in front (.i.e. { 0, 238, 348, 274, 490, 459, 349 })

Zip will take these 2 lists and apply the lambda provided to one element from each list. The lambda I provide there just formats it as a string, you can transform it anyway you want. I also make the lambda variable name a bit more verbose so you know what are those variables

Phuong Nguyen
  • 2,960
  • 1
  • 16
  • 25
  • The OP clarified that they don't want the "238-0" line, so just `string.Join("\r\n", items.Zip(items.Skip(1), (a, b) => $"{b} - {a} = {b - a}"))` will do for an answer using `Zip`. – Andrew Morton Sep 24 '20 at 10:14
  • @AndrewMorton thanks. I missed the comments. Yeah your comment should solve it in that case – Phuong Nguyen Sep 24 '20 at 10:17
0

Sure this can be done by (ab?)using the LINQ Aggregate method:

var differences = items.Aggregate(new { Previous = 0, List = new List<int>()}, (aggregate, current) =>
{
    aggregate.List.Add(current - aggregate.Previous);
    return new { Previous = current, aggregate.List};
}).List;

This creates a list of the differences you want. If you just want the Console.WriteLine you can just aggregate with an int value and output the differences directly.

But I hope this is just for knowledge, in practice imo in this case just stick to the for loop, much cleaner.

Another option if you want to use the LINQ ForEach and do your Console.WriteLine you could do something like this:

var previous = 0;
items.ForEach(item =>
{
    if (previous.HasValue)
        Console.WriteLine($"{item} - {previous} = {item - previous}");
    previous = item;
});

But once again nothing wrong with using the for loop. I really feel people go blind in their desire to use LINQ for everything.

EDIT: I see you asked to ignore the first "weird" output, basically skipping the first value, this is how that would look for both cases:

var differences = items.Aggregate(new { Previous = (int?)null, List = new List<int>()}, (aggregate, current) =>
{
    if (aggregate.Previous.HasValue)
        aggregate.List.Add(current - aggregate.Previous.Value);
    return new { Previous = (int?)current, aggregate.List};
}).List;

int? previous = null;
items.ForEach(item =>
{
    if (previous.HasValue)
        Console.WriteLine($"{item} - {previous} = {item - previous}");
    previous = item;
});
  • Using Aggregate() makes imho sense IF you pass in an accumulator function instead of writing the code inline. E.g. `items.Aggregate(MyAccumulators.DiffWithLast)`. Compared to extension functions you keep your intellisense clean. – Christoph Lütjen Sep 24 '20 at 10:35
0

@Christoph Lütjen Thank you for your advice. I tried and solved it.

int last = 0;
items.Aggregate(last, (prev,next) => {
    Console.WriteLine($"{next} - {prev} = {next - prev}");
    return next; 
}, next => last); 

result:
238 - 0 = 238
348 - 238 = 110
274 - 348 = -74
490 - 274 = 216
459 - 490 = -31
349 - 459 = -110

@AndrewMorton Thank you too.

var result = string.Join(Environment.NewLine, items.Zip(items.Skip(1), 
    (a, b) => $"{b} - {a} = {b - a}"));
Console.WriteLine(result);

//result
348 - 238 = 110
274 - 348 = -74
490 - 274 = 216
459 - 490 = -31
349 - 459 = -110