Использование LINQ для создания IEnumerable <> значений дельты

У меня есть список временных меток (в тиках), и из этого списка я хотел бы создать еще один, который представляет время дельта между записями.

Скажем, например, что мое мастер-расписание выглядит так:

  • 10
  • 20
  • 30
  • 50
  • 60
  • 70

Что я хочу, так это:

  • 10
  • 10
  • 20
  • 10
  • 10

То, что я пытаюсь выполнить здесь, - обнаружить, что # 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. Вы должны знать свой текущий индекс, чего нет, без какого-либо обходного пути.