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