代码之家  ›  专栏  ›  技术社区  ›  Dave

使用LINQ创建一个IEnumerable<>的delta值

  •  12
  • Dave  · 技术社区  · 15 年前

    我有一个时间戳列表(以刻度为单位),我想从这个列表中创建另一个表示条目之间的增量时间的列表。

    比如说,我的主时间表是这样的:

    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 回复  |  直到 15 年前
        1
  •  17
  •   Ani    15 年前

    如果您运行的是.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);
    
        2
  •  3
  •   Iain Galloway    15 年前

    你可以用阿尼的回答:-

    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
  •  3
  •   Steven    15 年前

    这应该可以做到:

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

    它不是真正的林肯,但将有效地做到这一点。

        4
  •  1
  •   Community Mohan Dere    9 年前

    看起来已经有足够的答案可以让你继续前进了,但是我在春天问了一个类似的问题:

    How to zip one ienumerable with itself

    在回答我的问题时,我了解到 Pairwise “和” Pairwise "

    我记得,显式地实现您自己的“成对”枚举器确实意味着您只需遍历一次列表,而用.Zip+.Skip(1)实现“成对”则意味着您最终将遍历列表两次。

    在我的文章中,我还包括一些几何(操作点列表)处理代码的示例,如长度/距离、面积、质心。

        5
  •  0
  •   Davy8    15 年前

    我不建议这么做,但完全滥用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
  •  0
  •   Viv    15 年前

    给你一行:

    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
  •  -2
  •   Joachim VR    15 年前

    LINQ并不是真正为您在这里要做的事情而设计的,因为它通常是按值计算的,很像for循环的一个非常有效的组合。 如果没有解决办法,你必须知道你当前的索引,有些是你不知道的。