我有一个 DateTime 项目列表,我想删除任何在 2 分钟内的项目(应该保留第一个遇到的项目)。有人可以告诉我如何使用 LINQ 实现这一目标吗?是否需要扩展方法?
为了澄清,一些示例数据:
00:00:00 00:01:30 00:02:30 00:05:00
应该返回:
00:00:00 00:05:00
所以这里的想法是首先对项目进行分组。在遍历列表中的项目(已排序)时,如果当前项目在前一个项目的阈值内,它应该进入同一个组,如果不是,它应该开始自己的组。
我们可以创建一个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
这是预期的输出。
当且仅当不存在小于 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;
}
}
非 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;
}
这是我测试过的另一个解决方案,它似乎有效:
//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());