我正在尝试使用LINQ为图表准备数据。
我无法解决的问题是如何计算“与之前的差异。
我期望的结果是
ID = 1,日期=现在,DiffToPrev = 0;
ID = 1,日期=现在+ 1,DiffToPrev = 3;
ID = 1,日期=现在+ 2,DiffToPrev = 7;
ID = 1,日期=现在+ 3,DiffToPrev = -6;
等...
你能帮我创建这样的查询吗?
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();
}
}
}
答案 0 :(得分:56)
一个选项(对于LINQ to Objects)将创建自己的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;
}
}
}
这使您可以仅使用源序列的一次传递来执行投影,这总是一个额外的好处(想象一下在大型日志文件上运行它)。
请注意,它会将长度为n
的序列投影到长度为n-1
的序列中 - 例如,您可能希望在前面添加“虚拟”第一个元素。 (或者更改方法以包含一个。)
以下是您如何使用它的示例:
var query = list.SelectWithPrevious((prev, cur) =>
new { ID = cur.ID, Date = cur.Date, DateDiff = (cur.Date - prev.Date).Days) });
请注意,这将包含一个ID的最终结果以及下一个ID的第一个结果...您可能希望先按ID对序列进行分组。
答案 1 :(得分:15)
使用index获取上一个对象:
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)
}
);
答案 2 :(得分:5)
在C#4中,您可以使用Zip方法一次处理两个项目。像这样:
var list1 = list.Take(list.Count() - 1);
var list2 = list.Skip(1);
var diff = list1.Zip(list2, (item1, item2) => ...);
答案 3 :(得分:4)
修改Jon Skeet的答案,不要跳过第一项:
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;
}
}
}
一些关键差异...传递第三个bool参数以指示它是否是可枚举的第一个元素。我还切换了当前/上一个参数的顺序。
以下是匹配示例:
var query = list.SelectWithPrevious((cur, prev, isfirst) =>
new {
ID = cur.ID,
Date = cur.Date,
DateDiff = (isfirst ? cur.Date : cur.Date - prev.Date).Days);
});
答案 4 :(得分:2)
Jon Skeet的版本的另一个模式(感谢您的解决方案+1)。除了这将返回一个可枚举的元组。
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;
}
}
}
这是 NOT 返回第一个,因为它是关于在项目之间返回中间值。
使用它像:
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;
// ..
}
OR
var newList = myObjectList.Intermediate().Select(item => item.Second.Date - item.First.Date);
或(如jon节目)
var newList = myObjectList.Intermediate().Select(item => new
{
ID = item.Second.ID,
Date = item.Second.Date,
DateDiff = (item.Second.Date - item.First.Date).Days
});
答案 5 :(得分:1)
继上面的Felix Ungman的帖子之后,下面是如何使用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)));
基本上,您正在压缩相同列表的两个版本,但第一个版本(当前列表)从集合中的第二个元素开始,否则差异将始终在同一元素上不同,给出差异为零。
我希望这是有道理的,
戴夫
答案 6 :(得分:1)
以下是使用readonly struct
和ValueTuple
(也称为struct
)的C#7.2重构代码。
我使用Zip()
创建由5个成员组成的(CurrentID, PreviousID, CurrDate, PrevDate, DiffToPrev)
元组。使用foreach
可以很容易地对其进行迭代:
foreach(var (CurrentID, PreviousID, CurrDate, PrevDate, DiffToPrev) in diffs)
完整代码:
public readonly struct S
{
public int ID { get; }
public DateTime Date { get; }
public int Value { get; }
public S(S other) => this = other;
public S(int id, DateTime date, int value)
{
ID = id;
Date = date;
Value = value;
}
public static void DumpDiffs(IEnumerable<S> list)
{
// Zip (or compare) list with offset 1 - Skip(1) - vs the original list
// this way the items compared are i[j+1] vs i[j]
// Note: the resulting enumeration will include list.Count-1 items
var diffs = list.Skip(1)
.Zip(list, (curr, prev) =>
(CurrentID: curr.ID, PreviousID: prev.ID,
CurrDate: curr.Date, PrevDate: prev.Date,
DiffToPrev: curr.Date.Day - prev.Date.Day));
foreach(var (CurrentID, PreviousID, CurrDate, PrevDate, DiffToPrev) in diffs)
Console.WriteLine($"Current ID: {CurrentID}, Previous ID: {PreviousID} " +
$"Current Date: {CurrDate}, Previous Date: {PrevDate} " +
$"Diff: {DiffToPrev}");
}
}
单元测试输出:
// the list:
// ID Date
// ---------------
// 233 17-Feb-19
// 122 31-Mar-19
// 412 03-Mar-19
// 340 05-May-19
// 920 15-May-19
// CurrentID PreviousID CurrentDate PreviousDate Diff (days)
// ---------------------------------------------------------
// 122 233 31-Mar-19 17-Feb-19 14
// 412 122 03-Mar-19 31-Mar-19 -28
// 340 412 05-May-19 03-Mar-19 2
// 920 340 15-May-19 05-May-19 10
请注意:struct
(尤其是readonly
)的性能要比class
更好。
感谢@FelixUngman和@DavidHuxtable的Zip()
想法!