query - Calcule la diferencia del artículo anterior con LINQ
linq to sql select (6)
Además de la publicación anterior de Felix Ungman, a continuación se muestra un ejemplo de cómo puede obtener los datos que necesita utilizando 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)));
Básicamente, está comprimiendo dos versiones de la misma lista, pero la primera versión (la lista actual) comienza en el segundo elemento de la colección, de lo contrario, una diferencia siempre diferiría del mismo elemento, dando una diferencia de cero.
Espero que esto tenga sentido,
Dave
Estoy tratando de preparar datos para un gráfico utilizando LINQ.
El problema que no puedo resolver es cómo calcular la diferencia con la anterior.
el resultado que espero es
ID = 1, Fecha = Ahora, DiffToPrev = 0;
ID = 1, Fecha = Ahora + 1, DiffToPrev = 3;
ID = 1, Fecha = Ahora + 2, DiffToPrev = 7;
ID = 1, Fecha = Ahora + 3, DiffToPrev = -6;
etc ...
¿Puedes ayudarme a crear tal consulta?
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<MyObject>
{
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();
}
}
}
En C # 4 puede usar el método Zip para procesar dos elementos a la vez. Me gusta esto:
var list1 = list.Take(list.Count() - 1);
var list2 = list.Skip(1);
var diff = list1.Zip(list2, (item1, item2) => ...);
Modificación de la respuesta de Jon Skeet para no omitir el primer elemento:
public static IEnumerable<TResult> SelectWithPrev<TSource, TResult>
(this IEnumerable<TSource> source,
Func<TSource, TSource, bool, TResult> 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;
}
}
}
Unas pocas diferencias clave ... pasa un tercer parámetro bool para indicar si es el primer elemento del enumerable. También cambié el orden de los parámetros actuales / anteriores.
Aquí está el ejemplo coincidente:
var query = list.SelectWithPrevious((cur, prev, isfirst) =>
new {
ID = cur.ID,
Date = cur.Date,
DateDiff = (isfirst ? cur.Date : cur.Date - prev.Date).Days);
});
Otro mod en la versión de Jon Skeet (gracias por su solución +1). Excepto que esto está devolviendo un número de tuplas.
public static IEnumerable<Tuple<T, T>> Intermediate<T>(this IEnumerable<T> source)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
T previous = iterator.Current;
while (iterator.MoveNext())
{
yield return new Tuple<T, T>(previous, iterator.Current);
previous = iterator.Current;
}
}
}
Esto NO es devolver el primero porque se trata de devolver el intermedio entre los elementos.
utilízalo como:
public class MyObject
{
public int ID { get; set; }
public DateTime Date { get; set; }
public int Value { get; set; }
}
var myObjectList = new List<MyObject>();
// 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 (como jon shows)
var newList = myObjectList.Intermediate().Select(item => new
{
ID = item.Second.ID,
Date = item.Second.Date,
DateDiff = (item.Second.Date - item.First.Date).Days
});
Una opción (para LINQ to Objects) sería crear su propio operador LINQ:
// I don''t like this name :(
public static IEnumerable<TResult> SelectWithPrevious<TSource, TResult>
(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> 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;
}
}
}
Esto le permite realizar su proyección utilizando solo una sola pasada de la secuencia de origen, lo que siempre es un bono (imagínese ejecutarlo sobre un archivo de registro grande).
Tenga en cuenta que proyectará una secuencia de longitud n
en una secuencia de longitud n-1
; es posible que desee anteponer un primer elemento "ficticio", por ejemplo. (O cambie el método para incluir uno.)
Aquí hay un ejemplo de cómo lo usarías:
var query = list.SelectWithPrevious((prev, cur) =>
new { ID = cur.ID, Date = cur.Date, DateDiff = (cur.Date - prev.Date).Days) });
Tenga en cuenta que esto incluirá el resultado final de una ID con el primer resultado de la próxima ID ... es posible que desee agrupar su secuencia por ID primero.
Usa el índice para obtener el objeto anterior:
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)
}
);