我可以使用LINQ仅检索“on change”值吗?

时间:2010-02-03 19:19:33

标签: linq

我希望能够做的是构建一个LINQ查询,当其中一个字段发生变化时,它会从某些DataRows中检索一些值。这是一个人为的例子来说明:

Observation   Temp  Time
------------- ----  ------
Cloudy        15.0  3:00PM
Cloudy        16.5  4:00PM
Sunny         19.0  3:30PM
Sunny         19.5  3:15PM
Sunny         18.5  3:30PM
Partly Cloudy 16.5  3:20PM
Partly Cloudy 16.0  3:25PM
Cloudy        16.0  4:00PM
Sunny         17.5  3:45PM

当观察从上一个观察发生变化时,我只想检索条目。所以结果将包括:

Cloudy        15.0  3:00PM
Sunny         19.0  3:30PM
Partly Cloudy 16.5  3:20PM
Cloudy        16.0  4:00PM
Sunny         17.5  3:45PM

目前有代码遍历DataRows并进行结果的比较和构建,但希望使用LINQ来实现这一目标。

我想做的是这样的事情:

var weatherStuff = from row in ds.Tables[0].AsEnumerable()
                   where row.Field<string>("Observation") != weatherStuff.ElementAt(weatherStuff.Count() - 1) )
                   select row;

但这不起作用 - 并且不会编译,因为它在声明之前尝试使用变量'weatherStuff'。

我想用LINQ做什么?我在SO上没有看到像这样的另一个问题,但可能错过了它。

5 个答案:

答案 0 :(得分:7)

这是一个更普遍的想法,可能是相互影响。它比@tvanfosson发布的更复杂,但在某种程度上,我觉得它更优雅:-)。您要执行的操作是使用第一个字段对观察结果进行分组,但是每次值更改时您都希望启动一个新组。然后,您要选择每个组的第一个元素。

这听起来几乎像LINQ的group by,但它有点不同,所以你不能真正使用标准的group by。但是,您可以编写自己的版本(这是LINQ的奇迹!)。您可以编写自己的扩展方法(例如GroupByMoving),也可以编写扩展方法,将类型从IEnumerable更改为某个接口,然后为此接口定义GroupBy。生成的查询将如下所示:

var weatherStuff = 
  from row in ds.Tables[0].AsEnumerable().AsMoving()
  group row by row.Field<string>("Observation") into g
  select g.First();

唯一剩下的就是定义AsMoving并实施GroupBy。这有点工作,但它通常是有用的东西,它也可以用来解决其他问题,所以它可能值得这样做:-)。我的帖子的摘要是关于LINQ的好处是你可以自定义操作符的行为以获得非常优雅的代码。

我还没有测试过,但实现应该是这样的:

// Interface & simple implementation so that we can change GroupBy
interface IMoving<T> : IEnumerable<T> { }
class WrappedMoving<T> : IMoving<T> {
  public IEnumerable<T> Wrapped { get; set; }
  public IEnumerator<T> GetEnumerator() { 
    return Wrapped.GetEnumerator(); 
  }
  public IEnumerator<T> GetEnumerator() { 
    return ((IEnumerable)Wrapped).GetEnumerator(); 
  }
}

// Important bits:
static class MovingExtensions { 
  public static IMoving<T> AsMoving<T>(this IEnumerable<T> e) {
    return new WrappedMoving<T> { Wrapped = e };
  }

  // This is (an ugly & imperative) implementation of the 
  // group by as described earlier (you can probably implement it
  // more nicely using other LINQ methods)
  public static IEnumerable<IEnumerable<T>> GroupBy<T, K>(this IEnumerable<T> source, 
       Func<T, K> keySelector) {
    List<T> elementsSoFar = new List<T>();
    IEnumerator<T> en = source.GetEnumerator();
    if (en.MoveNext()) {
      K lastKey = keySelector(en.Current);
      do { 
        K newKey = keySelector(en.Current);
        if (newKey != lastKey) { 
          yield return elementsSoFar;
          elementsSoFar = new List<T>();
        }
        elementsSoFar.Add(en.Current);
      } while (en.MoveNext());
      yield return elementsSoFar;
    }
  }

答案 1 :(得分:4)

您可以使用带索引的IEnumerable扩展名。

var all = ds.Tables[0].AsEnumerable();
var weatherStuff = all.Where( (w,i) => i == 0 || w.Field<string>("Observation") != all.ElementAt(i-1).Field<string>("Observation") );

答案 2 :(得分:1)

这是迭代解决方案在可读性和性能方面实际上优于基于集合的解决方案的那些实例之一。你真正希望Linq做的就是过滤并预先对列表进行排序,以便为循环做好准备。

可以使用窗口函数(ROW_NUMBER)在SQL Server(或其他各种数据库)中编写查询,如果这是您的数据来源,但在纯Linq中很难做到更大的混乱。


如果您只是想清理代码,扩展方法可能会有所帮助:

public static IEnumerable<T> Changed(this IEnumerable<T> items,
    Func<T, T, bool> equalityFunc)
{
    if (equalityFunc == null)
    {
        throw new ArgumentNullException("equalityFunc");
    }
    T last = default(T);
    bool first = true;
    foreach (T current in items)
    {
        if (first || !equalityFunc(current, last))
        {
            yield return current;
        }
        last = current;
        first = false;
    }
}

然后你可以用:

来调用它
var changed = rows.Changed((r1, r2) =>
    r1.Field<string>("Observation") == r2.Field<string>("Observation"));

答案 3 :(得分:0)

我认为你想要实现的目标是不可能使用“syntax suggar”。但是,可以使用传递您正在评估的项目的索引的扩展方法Select。因此,您可以使用索引将当前项与前一项进行比较(索引-1)。

答案 4 :(得分:0)

You could useMorelinq's GroupAdjacent() extension method

GroupAdjacent: Groups the adjacent elements of a sequence according to a specified key selector function...This method has 4 overloads.

You would use it like this with the result selector overload to lose the IGrouping key:-

var weatherStuff = ds.Tables[0].AsEnumerable().GroupAdjacent(w => w.Field<string>("Observation"), (_, val) => val.Select(v => v));

This is a very popular extension to default Linq methods, with more than 1M downloads on Nuget (compared to MS's own Ix.net with ~40k downloads at time of writing)