-3

我有一个 DateTime 项目列表,我想删除任何在 2 分钟内的项目(应该保留第一个遇到的项目)。有人可以告诉我如何使用 LINQ 实现这一目标吗?是否需要扩展方法?

为了澄清,一些示例数据:

00:00:00 00:01:30 00:02:30 00:05:00

应该返回:

00:00:00 00:05:00

4

4 回答 4

3

所以这里的想法是首先对项目进行分组。在遍历列表中的项目(已排序)时,如果当前项目在前一个项目的阈值内,它应该进入同一个组,如果不是,它应该开始自己的组。

我们可以创建一个GroupWhile函数,该函数接受一个给定先前和当前项目的函数,并确定它们是否应该组合在一起。我们对数据进行排序,根据提供的条件进行分组,然后取出每组中的第一项。

public static IEnumerable<DateTime> LoneDates(
    IEnumerable<DateTime> dates, TimeSpan threshold)
{
    return dates.OrderBy(x => x)
        .GroupWhile((previous, current) => current - previous <= threshold)
        .Select(group => group.First());
}

至于实现GroupWhile,可以这样做:

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
    this IEnumerable<T> source, Func<T, T, bool> predicate)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        List<T> list = new List<T>() { iterator.Current };

        T previous = iterator.Current;

        while (iterator.MoveNext())
        {
            if (predicate(previous, iterator.Current))
            {
                list.Add(iterator.Current);
            }
            else
            {
                yield return list;
                list = new List<T>() { iterator.Current };
            }

            previous = iterator.Current;
        }
        yield return list;
    }
}

通读它,它将第一个项目放在自己的组中,然后遍历序列中的每个其他项目;如果给定的函数说它应该被添加到当前组中,如果不是,则当前组被发送到输出序列并创建一个新组。

使用您的示例输入:

var data = new List<DateTime>()
{
    DateTime.Today,
    DateTime.Today.AddMinutes(1.5),
    DateTime.Today.AddMinutes(2.5),
    DateTime.Today.AddMinutes(5),
};

var query = LoneDates(data, TimeSpan.FromMinutes(2));

Console.WriteLine(string.Join("\n", query));

结果是:

2013 年 8 月 30 日上午 12:00:00

2013 年 8 月 30 日上午 12:05:00

这是预期的输出。

于 2013-08-30T15:15:09.777 回答
0

询问

当且仅当不存在小于 2 秒之前的直接时间时,此查询将为您提供时间 T。

IEnumerable<DateTime> times = ...;

var query = times
    .OrderBy(x => x)
    .Throttle((x, y) => y.Subtract(x) <= TimeSpan.FromSeconds(2));

帮手

public static IEnumerable<T> Throttle(
    this IEnumerable<T> source, Func<T, T, bool> collapse)
{
    var first = true;
    var prev = default(T);
    foreach (var curr in source)
    {
        if (first || !collapse(prev, curr))
        {
            yield return curr;
            first = false;
        }
        prev = curr;
    }
}
于 2013-08-30T15:17:52.460 回答
0

非 Linq 的答案,似乎是直截了当的。

 private List<DateTime> RemoveItems(List<DateTime> times)
        {
            var newtimes = new List<DateTime>();

            var previoustime = new DateTime();

            var firsttime = times[0];

            newtimes.Add(firsttime);

            foreach (var time in times)
            {
                if (firsttime == time)
                {
                    previoustime = time;
                    continue;
                }

                if ((time - previoustime) > new TimeSpan(0,0,1,30))
                {
                    newtimes.Add(time);
                }

                previoustime = time;
            }

            return newtimes;
        }
于 2013-08-30T16:12:06.913 回答
0

这是我测试过的另一个解决方案,它似乎有效:

//build the data to test
List<DateTime> data = new List<DateTime>();
Random rand = new Random();
for (int i = 0; i < 50; i++) {
   data.Add(new DateTime(2013, 12, 22, 12, rand.Next(50),0));
}
//----------
DateTime fix = DateTime.Now;
int j = 0;
var result = data.OrderBy(x => x)
                         .Select((x,i)=>new{x,i})
                         .GroupBy(x=> {
                             if(x.i == 0) fix = x.x;
                             else if ((x.x - fix).TotalMinutes >= 2)
                             {
                                 fix = x.x;
                                 j++;
                             }
                             return j;
                            }, e=>e.x, (key,e)=>e.First());
于 2013-08-31T02:04:33.353 回答