是否可以从集合中删除“类似”项目?

时间:2013-08-30 15:09:55

标签: c# .net linq

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

澄清一些样本数据:

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

应该返回:

00:00:00 00:05:00

4 个答案:

答案 0 :(得分: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

     

8/30/2013 12:05:00 AM

这是预期的输出。

答案 1 :(得分: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;
    }
}

答案 2 :(得分:0)

非林克回答,似乎很直接。

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

答案 3 :(得分: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());