Использование LINQ для создания IEnumerable <> дельта-значений - PullRequest
12 голосов
/ 06 октября 2010

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

Скажем так, например, что мое расписание мастер выглядит следующим образом:

  1. 10
  2. 20
  3. 30
  4. 50
  5. 60
  6. 70

Что я хочу вернуть, так это:

  1. 10
  2. 10
  3. 20
  4. 10
  5. 10

То, что я пытаюсь сделать здесь, это обнаружить, что № 3 в выходной таблице является выбросом путем вычисления стандартного отклонения. Раньше я не брал статистику, но думаю, что если я найду преобладающее значение в списке вывода и выброшу что-нибудь за пределами 1 сигмы, это будет работать для меня адекватно.

Я бы хотел создать список вывода с помощью одного запроса LINQ, но я еще не понял этого. В настоящее время я просто грубо заставляю его петлей.

Ответы [ 7 ]

16 голосов
/ 06 октября 2010

Если вы используете .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 может не быть хорошей идеей: квадратичное время выполнения будет происходить для не 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);
3 голосов
/ 06 октября 2010

Вы можете использовать ответ Ани: -

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.

3 голосов
/ 06 октября 2010

Это должно сработать:

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, но эффективно сработает.

1 голос
/ 06 октября 2010

Похоже, что уже достаточно ответов, чтобы вы пошли уже, но я задал похожий вопрос еще весной:

Как застегнуть один неисчислимый с собой

В ответах на мой вопрос я узнал о « Pairwise » и « Pairwise »

Как я помню, явная реализация собственного перечислителя «Pairwise» делаетозначает, что вы повторяете свой список ровно один раз, тогда как реализация "Pairwise" в терминах .Zip + .Skip (1) означает, что в конечном итоге вы будете повторять свой список дважды.

В своем посте я также включаю несколько примеровгеометрии (работа со списками точек), обработка кода, такого как длина / расстояние, площадь, центр тяжести.

0 голосов
/ 06 октября 2010

Один лайнер для вас:

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]);
0 голосов
/ 06 октября 2010

Не то, чтобы я рекомендовал это, но полностью злоупотребляя 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);
}
0 голосов
/ 06 октября 2010

LINQ на самом деле не предназначен для того, что вы пытаетесь сделать здесь, потому что он обычно оценивает значение за значением, во многом как чрезвычайно эффективная комбинация циклов for. Вы должны знать свой текущий индекс, а то, чего не знаете, без какого-либо обходного пути.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...