Использование LINQ для создания IEnumerable <> значений дельты
У меня есть список временных меток (в тиках), и из этого списка я хотел бы создать еще один, который представляет время дельта между записями.
Скажем, например, что мое мастер-расписание выглядит так:
Что я хочу, так это:
То, что я пытаюсь выполнить здесь, - обнаружить, что # 3 в выходной таблице является outlier, вычисляя стандартное отклонение. Раньше я не занимался статистикой, но думаю, что если я буду искать распространенное значение в выходном списке и выкинуть что-нибудь за пределами сигмы, это будет для меня адекватным.
Мне бы очень хотелось создать список вывода с помощью одного запроса LINQ, но я еще не понял его. В настоящее время я просто грубо заставляю его с помощью цикла.
Ответы
Ответ 1
Если вы используете .NET 4.0, это должно работать нормально:
var deltas = list.Zip(list.Skip(1), (current, next) => next - current);
Помимо множества счетчиков это довольно эффективно; он должен хорошо работать в любой последовательности.
Здесь альтернатива для .NET 3.5:
var deltas = list.Skip(1)
.Select((next, index) => next - list[index]);
Очевидно, что эта идея будет эффективна только при использовании индексатора списков. Модифицировать его для использования ElementAt
может не очень хорошая идея: будет выполняться квадратичное время выполнения для последовательностей non IList<T>
. В этом случае создание пользовательского итератора является хорошим решением.
РЕДАКТИРОВАТЬ. Если вам не нравится идея Zip
+ Skip(1)
, запись такого расширения, как эта (непроверенная), может быть полезна в таких обстоятельствах:
public class CurrentNext<T>
{
public T Current { get; private set; }
public T Next { get; private set; }
public CurrentNext(T current, T next)
{
Current = current;
Next = next;
}
}
...
public static IEnumerable<CurrentNext<T>> ToCurrentNextEnumerable<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentException("source");
using (var source = enumerable.GetEnumerator())
{
if (!enumerator.MoveNext())
yield break;
T current = enumerator.Current;
while (enumerator.MoveNext())
{
yield return new CurrentNext<T>(current, enumerator.Current);
current = enumerator.Current;
}
}
}
Что вы могли бы использовать как:
var deltas = list.ToCurrentNextEnumerable()
.Select(c=> c.Next - c.Current);
Ответ 2
Это должно сделать трюк:
static IEnumerable<int> GetDeltas(IEnumerable<int> collection)
{
int? previous = null;
foreach (int value in collection)
{
if (previous != null)
{
yield return value - (int)previous;
}
previous = value;
}
}
Теперь вы можете позвонить в свою коллекцию следующим образом:
var masterTimetable = GetMasterTimeTable();
var deltas = GetDeltas(masterTimetable);
Это не LINQ, но эффективно выполнит трюк.
Ответ 3
Вы можете использовать ответ Ани: -
var deltas = list.Zip(list.Skip(1), (current, next) => next - current);
С суперпростой реализацией метода расширения Zip: -
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> func)
{
var ie1 = first.GetEnumerator();
var ie2 = second.GetEnumerator();
while (ie1.MoveNext() && ie2.MoveNext())
yield return func(ie1.Current, ie2.Current);
}
Это будет работать с 3.5.
Ответ 4
Похоже, что есть достаточные ответы, чтобы вы уже начали, но я задал аналогичный вопрос в spring:
Как закрепить один ienumerable с собой
В ответах на мой вопрос я узнал о " Pairwise" и "парный"
Как я помню, явное выполнение вашего собственного перечислителя "Pairwise" означает, что вы перебираете список через ровно один раз, тогда как реализация "Pairwise" с точки зрения .Zip +.Skip(1) означает, что вы в конечном счете перейдете через свой список в два раза.
В моем посте я также включаю несколько примеров обработки геометрии (работающих по спискам точек), таких как длина/расстояние, область, центрроид.
Ответ 5
Не то, чтобы я рекомендую это, но полностью злоупотребляя LINQ, будет работать следующее:
var vals = new[] {10, 20, 30, 50, 60, 70};
int previous = 0;
var newvals = vals.Select(i =>
{
int dif = i - previous;
previous = i;
return dif;
});
foreach (var newval in newvals)
{
Console.WriteLine(newval);
}
Ответ 6
Один вкладыш для вас:
int[] i = new int[] { 10, 20, 30, 50, 60, 70 };
IEnumerable<int> x = Enumerable.Range(1, i.Count()-1).Select(W => i[W] - i[W - 1]);
Ответ 7
LINQ на самом деле не предназначен для того, что вы пытаетесь сделать здесь, потому что он обычно оценивает значение по значению, как чрезвычайно эффективную комбинацию for-loops.
Вы должны знать свой текущий индекс, чего нет, без какого-либо обходного пути.