具有偏移量C#Linq的GroupBy DateTime列

时间:2017-06-15 13:40:33

标签: c# .net linq

在我的.NET 4.5.2 C#控制台应用中,我有List<SomeItem>名为someItemsSomeItem为:

public class SomeItem    {
    public int A { get; set; }
    public int B { get; set; }
    public DateTime C { get; set; }
}

然后我根据AB将这些项目分组到一个新列表中:

var groupedItems = someItems.GroupBy(x => new { x.A, x.B });

我现在希望将C介绍到GroupBy,但有一个问题:我想将SomeItems属性在+ - 内的C组合在一起 - (加或减)彼此相差2秒(基本上是2秒的偏移)。因此,例如,两个SomeItem,其中一个C设置为 2016-03-28 17:58:01.0002016-03-28 17:58:03.000的另一个将组合在一起。这可能吗?

编辑:出于所有意图和目的,我们假设列表中不存在可能导致重叠的项目。小组(就像@J.Steen在评论中提出的问题)

编辑2:示例

假设以下数据:

  • 15/06/2017 00:00:02
  • 15/06/2017 00:00:04
  • 15/06/2017 00:00:06
  • 15/06/2017 00:00:09
  • 15/06/2017 00:00:11
  • 15/06/2017 00:00:15

..和2秒的偏移量,我希望它们按以下方式分组:

第1组:

  • 15/06/2017 00:00:02
  • 15/06/2017 00:00:04
  • 15/06/2017 00:00:06

第2组:

  • 15/06/2017 00:00:09
  • 15/06/2017 00:00:11

第3组:

  • 15/06/2017 00:00:15

4 个答案:

答案 0 :(得分:1)

您可以创建一个扩展方法来舍入秒数。我找到了这个例子: Have datetime.now return to the nearest second

该问题的答案是:

public static DateTime Trim(this DateTime date, long ticks) {
   return new DateTime(date.Ticks - (date.Ticks % ticks), date.Kind);
}

然后你可以在小组中做这样的事情:

var result = dates.GroupBy(x => x.Trim(TimeSpan.FromSeconds(3).Ticks));

所以给出输入

  • 15/06/2017 00:00:01
  • 15/06/2017 00:00:02
  • 15/06/2017 00:00:03
  • 15/06/2017 00:00:04
  • 15/06/2017 00:00:05
  • 15/06/2017 00:00:06

你将有3组:

A组

  1. 15/06/2017 00:00:01
  2. 15/06/2017 00:00:02
  3. B组

    1. 15/06/2017 00:00:03
    2. 15/06/2017 00:00:04
    3. 15/06/2017 00:00:05
    4. C组:

      1. 15/06/2017 00:00:06

答案 1 :(得分:1)

下面的代码可以解决问题。

基本上,它会在多个传递中进行分组 - 首先在A和B上进行分组,然后使用TakeWhileSkip按照您希望对其进行分组的方式对日期进行分组。< / p>

using System;
using System.Collections.Generic;
using System.Linq;

namespace Test
{
    public class SomeItem
    {
        public int A { get; set; }
        public int B { get; set; }
        public DateTime C { get; set; }

        public override string ToString()
        {
            return $"{A} - {B} - {C}";
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            // Assuming 2 second margin
            var margin = TimeSpan.FromSeconds(2);

            var input = new List<SomeItem>
            {
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 0)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 2)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 4)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 6)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 9)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 11)},
                new SomeItem() {A = 1, B = 2, C = new DateTime(2017, 6, 15, 0, 0, 15)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 2)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 4)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 6)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 9)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 11)},
                new SomeItem() {A = 1, B = 3, C = new DateTime(2017, 6, 15, 0, 0, 13)}
            };

            var firstGrouping = input.GroupBy(x => new { x.A, x.B });
            var readyForGrouping = new List<Tuple<SomeItem, int>>();

            foreach (var grouping in firstGrouping)
            {
                var data = grouping.OrderBy(z => z.C).ToList();
                var lastDate = default(DateTime?);
                var count = 0;
                var groupingCount = data.Count();
                var groupID = 0;

                while (groupingCount > count)
                {
                    groupID++;
                    readyForGrouping.AddRange(data.Skip(count).TakeWhile(z =>
                    {
                        var old = lastDate;
                        lastDate = z.C;
                        if (old == null)
                        {
                            count++;
                            return true;
                        }
                        if (z.C <= old.Value.Add(margin))
                        {
                            count++;
                            return true;
                        }
                        return false;
                    }).Select(z => new Tuple<SomeItem, int>(z, groupID)).ToList());
                }
            }

            var groupedItems = readyForGrouping.GroupBy(x => new { x.Item1.A, x.Item1.B, x.Item2 },
                x => x.Item1);

            foreach (var grouping in groupedItems)
            {
                Console.WriteLine("Start Of Group");
                foreach (var entry in grouping)
                {
                    Console.WriteLine(entry);

                }
            }
            Console.ReadLine();
        }
    }
}

答案 2 :(得分:1)

您的示例输入和输出有点混乱。

您的前3个项目2,4和6有重叠。 4可以同时属于2和6.这意味着要决定分组,您需要决定分组将基于哪个项目。如果以2开始分组,那么结果将是

  • 第1组:2,4
  • 第2组:6,
  • 第3组:9,11
  • 第4组:15

似乎你已经应用了你的人类大脑,看看如果起点是4秒,实际上6可以加入第一组。然后小组成为:

  • 第1组:2,4,6
  • 第2组:9,11
  • 第3组:15

您可以创建IEqualityComparer<DateTime>来执行此操作,但您的分组将取决于集合的顺序:

public class GroupingComparer : IEqualityComparer<DateTime>
{
    private readonly int _offset;

    public GroupingComparer(int offset)
    {
        _offset = offset;
    }

    public bool Equals(DateTime x, DateTime y)
    {
        if (y.Second >= x.Second - _offset && y.Second <= x.Second + _offset) return true;

        return false;
    }

    public int GetHashCode(DateTime obj)
    {
        //Should most probably look at a better way to get the hashcode.
        return obj.ToShortDateString().GetHashCode();
    }
}

像这样使用它:

GroupingComparer comparer = new GroupingComparer(offset:2);
var result2 = dates.GroupBy(x => x, comparer).ToList(); 

所以现在一切都取决于你想做什么。您可以通过更改集合的顺序来获得上述任一输出。但是,如果您在应用程序的不同部分使用不同的顺序,这可能意味着应用程序中的奇怪行为。也许扩展方法OrderAndGroup可以解决此问题。

答案 3 :(得分:0)

这是不可能的,因为GroupBy将相同的项目组合在一起。你可以添加一个新的属性&#34; flattens&#34;次。

所以说出类似的话 public DateTime FlatDate{get{ return C.ToString("yyyy-MM-dd HH:mm");}}

你可以根据你想要分组的方式来改变回报,所以如果第二个是&lt; 3将其设为0,&lt; 6将其设置为3等。

不是最优雅,但会做你想做的事。