代码之家  ›  专栏  ›  技术社区  ›  Marty

使用LINQ计算与上一项的差异

  •  17
  • Marty  · 技术社区  · 16 年前

    我正在尝试使用LINQ为图表准备数据。

    我无法解决的问题是如何计算“与前一个的差异”。

    我期望的结果是

    id=1,date=now,difftoprev=0;

    id=1,date=now+1,difftopRev=3;

    id=1,date=now+2,difftopRev=7;

    id=1,date=now+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();
            }
        }
    }
    
    6 回复  |  直到 8 年前
        1
  •  56
  •   Mitch Wheat    10 年前

    一个选项(对于Linq to对象)是创建自己的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 -例如,您可能需要在第一个元素前面加上“dummy”。(或将方法更改为包含一个。)

    下面是一个如何使用它的示例:

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

    请注意,这将包括一个ID的最终结果和下一个ID的第一个结果…您可能希望先按ID对序列进行分组。

        2
  •  15
  •   Branimir    13 年前

    使用索引获取上一个对象:

       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)
              }
       );
    
        3
  •  5
  •   Felix Ungman    16 年前

    在C 4中,您可以使用zip方法一次处理两个项目。这样地:

            var list1 = list.Take(list.Count() - 1);
            var list2 = list.Skip(1);
            var diff = list1.Zip(list2, (item1, item2) => ...);
    
        4
  •  4
  •   Edyn    11 年前

    修改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);
        });
    
        5
  •  2
  •   Jeroen van Langen    8 年前

    另一个模式开启 乔恩-斯基特 版本(感谢您的解决方案+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;
            }
        }
    }
    

    这是 不是 返回第一个,因为它是关于返回项目之间的中间值。

    像这样使用它:

    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;
        // ..
    }
    

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

    (就像乔恩的表演)

    var newList = myObjectList.Intermediate().Select(item => new 
    { 
        ID = item.Second.ID, 
        Date = item.Second.Date, 
        DateDiff = (item.Second.Date - item.First.Date).Days
    });
    
        6
  •  1
  •   David Huxtable    13 年前

    关于上面的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)));
    

    基本上,您正在压缩同一列表的两个版本,但第一个版本(当前列表)从集合中的第二个元素开始,否则,同一个元素之间的差异将始终不同,使差异为零。

    我希望这是有道理的,

    戴夫

    推荐文章