Calcola la differenza rispetto all’articolo precedente con LINQ

Sto cercando di preparare i dati per un grafico usando LINQ.

Il problema che non posso risolvere è come calcolare la “differenza rispetto al precedente”.

il risultato che mi aspetto è

ID = 1, Data = Now, DiffToPrev = 0;

ID = 1, Data = Ora + 1, DiffToPrev = 3;

ID = 1, Data = Ora + 2, DiffToPrev = 7;

ID = 1, Data = Ora + 3, DiffToPrev = -6;

eccetera…

Potete aiutarmi a creare una tale domanda?

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { public class MyObject { public int ID { get; set; } public DateTime Date { get; set; } public int Value { get; set; } } class Program { static void Main() { var list = new List { new MyObject {ID= 1,Date = DateTime.Now,Value = 5}, new MyObject {ID= 1,Date = DateTime.Now.AddDays(1),Value = 8}, new MyObject {ID= 1,Date = DateTime.Now.AddDays(2),Value = 15}, new MyObject {ID= 1,Date = DateTime.Now.AddDays(3),Value = 9}, new MyObject {ID= 1,Date = DateTime.Now.AddDays(4),Value = 12}, new MyObject {ID= 1,Date = DateTime.Now.AddDays(5),Value = 25}, new MyObject {ID= 2,Date = DateTime.Now,Value = 10}, new MyObject {ID= 2,Date = DateTime.Now.AddDays(1),Value = 7}, new MyObject {ID= 2,Date = DateTime.Now.AddDays(2),Value = 19}, new MyObject {ID= 2,Date = DateTime.Now.AddDays(3),Value = 12}, new MyObject {ID= 2,Date = DateTime.Now.AddDays(4),Value = 15}, new MyObject {ID= 2,Date = DateTime.Now.AddDays(5),Value = 18} }; Console.WriteLine(list); Console.ReadLine(); } } } 

Un’opzione (per LINQ to Objects) sarebbe quella di creare il proprio operatore LINQ:

 // I don't like this name :( public static IEnumerable SelectWithPrevious (this IEnumerable source, Func projection) { using (var iterator = source.GetEnumerator()) { if (!iterator.MoveNext()) { yield break; } TSource previous = iterator.Current; while (iterator.MoveNext()) { yield return projection(previous, iterator.Current); previous = iterator.Current; } } } 

Ciò consente di eseguire la proiezione utilizzando solo un singolo passaggio della sequenza sorgente, che è sempre un vantaggio (immagina di eseguirlo su un file di registro di grandi dimensioni).

Nota che proietterà una sequenza di lunghezza n in una sequenza di lunghezza n-1 – potresti voler anteporre un primo elemento “fittizio”, per esempio. (Oppure cambia il metodo per includerne uno.)

Ecco un esempio di come lo useresti:

 var query = list.SelectWithPrevious((prev, cur) => new { ID = cur.ID, Date = cur.Date, DateDiff = (cur.Date - prev.Date).Days) }); 

Nota che questo includerà il risultato finale di un ID con il primo risultato del prossimo ID … potresti voler prima raggruppare la sequenza per ID.

Usa l’indice per ottenere l’object precedente:

  var LinqList = list.Select( (myObject, index) => new { ID = myObject.ID, Date = myObject.Date, Value = myObject.Value, DiffToPrev = (index > 0 ? myObject.Value - list[index - 1].Value : 0) } ); 

In C # 4 è ansible utilizzare il metodo Zip per elaborare due elementi alla volta. Come questo:

  var list1 = list.Take(list.Count() - 1); var list2 = list.Skip(1); var diff = list1.Zip(list2, (item1, item2) => ...); 

Modifica della risposta di Jon Skeet per non saltare il primo object:

 public static IEnumerable SelectWithPrev (this IEnumerable source, Func projection) { using (var iterator = source.GetEnumerator()) { var isfirst = true; var previous = default(TSource); while (iterator.MoveNext()) { yield return projection(iterator.Current, previous, isfirst); isfirst = false; previous = iterator.Current; } } } 

Alcune differenze chiave … passa un terzo parametro bool per indicare se è il primo elemento dell’enumerabile. Ho anche cambiato l’ordine dei parametri attuali / precedenti.

Ecco l’esempio di corrispondenza:

 var query = list.SelectWithPrevious((cur, prev, isfirst) => new { ID = cur.ID, Date = cur.Date, DateDiff = (isfirst ? cur.Date : cur.Date - prev.Date).Days); }); 

Ancora un altro mod sulla versione di Jon Skeet (grazie per la tua soluzione +1). Tranne che questo sta restituendo una enumerabile di tuple.

 public static IEnumerable> Intermediate(this IEnumerable source) { using (var iterator = source.GetEnumerator()) { if (!iterator.MoveNext()) { yield break; } T previous = iterator.Current; while (iterator.MoveNext()) { yield return new Tuple(previous, iterator.Current); previous = iterator.Current; } } } 

Questo NON sta restituendo il primo perché si tratta di restituire l’intermedio tra gli elementi.

usalo come:

 public class MyObject { public int ID { get; set; } public DateTime Date { get; set; } public int Value { get; set; } } var myObjectList = new List(); // don't forget to order on `Date` foreach(var deltaItem in myObjectList.Intermediate()) { var delta = deltaItem.Second.Offset - deltaItem.First.Offset; // .. } 

O

 var newList = myObjectList.Intermediate().Select(item => item.Second.Date - item.First.Date); 

O (come gli spettacoli di jon)

 var newList = myObjectList.Intermediate().Select(item => new { ID = item.Second.ID, Date = item.Second.Date, DateDiff = (item.Second.Date - item.First.Date).Days }); 

Oltre al post di Felix Ungman sopra, di seguito è riportato un esempio di come è ansible ottenere i dati necessari utilizzando Zip ():

  var diffs = list.Skip(1).Zip(list, (curr, prev) => new { CurrentID = curr.ID, PreviousID = prev.ID, CurrDate = curr.Date, PrevDate = prev.Date, DiffToPrev = curr.Date.Day - prev.Date.Day }) .ToList(); diffs.ForEach(fe => Console.WriteLine(string.Format("Current ID: {0}, Previous ID: {1} Current Date: {2}, Previous Date: {3} Diff: {4}", fe.CurrentID, fe.PreviousID, fe.CurrDate, fe.PrevDate, fe.DiffToPrev))); 

Fondamentalmente, stai zippando due versioni della stessa lista ma la prima versione (la lista corrente) inizia dal 2 ° elemento della collezione, altrimenti una differenza sarebbe sempre diversa per lo stesso elemento, dando una differenza di zero.

Spero che abbia senso,

Dave