我在C#中有一个列表,该列表具有两个datetime字段:DatePaid和DateEntered。我想查找乱序元素。这很容易,老派。按DatePaid对列表进行排序,然后遍历列表,将DateEntered捕获到局部变量(lastDateEntered)中。每次循环时,我们首先比较DateEntered和lastDateEntered。如果DateEntered小于lastDateEntered,则上一行将是乱序行。

| DatePaid | DateEntered | comments     |
|----------|-------------|--------------|
| 1/1/2019 | 1/1/2019    |              |
| 2/1/2019 | 2/2/2019    |              |
| 3/1/2019 | 3/1/2019    |              |
| 4/1/2019 | 5/2/2019    | out of order |
| 5/1/2019 | 5/1/2019    |              |


在SQL中(如果您不关心SQL,请忽略此段),创建两个CTE's(就像子查询)很容易:一个按DatePaid排序,另一个按DateEntered排序。我们使用Row_Number()函数添加已对CTE排序的额外字段。然后,我们加入每个CTE的行号,然后仅选择日期从一个CTE到另一个CTE不相等的行。与Oracle 12c - sql to find out of order rows相似,尽管答案没有使用CTE。我想我可以通过linq来做些类似的事情,但我不确定它会比foreach循环方法容易。

有没有更好的Linqish方法?

最佳答案

使用基于APL扫描运算符的LINQ扩展(如Aggregate,仅返回中间结果),该扩展将列表的prev和cur元素组合在一起以获得新值,解决方案很简单。

一,扩展方法:

// TRes combineFn(T prevValue, T curValue)
public static IEnumerable<TRes> ScanByPairs<T, TRes>(this IEnumerable<T> src, Func<T, T, TRes> combineFn) {
    using (var srce = src.GetEnumerator())
        if (srce.MoveNext()) {
            var prev = srce.Current;

            while (srce.MoveNext())
                yield return combineFn(prev, prev = srce.Current);
        }
}


现在您可以测试每个日期字段:

var ansdp = list.ScanByPairs((prev, cur) => new { OrderNotOkay = prev.DatePaid >= cur.DatePaid, prev })
              .Where(op => op.OrderNotOkay)
              .Select(op => op.prev)
              .ToList();

var ansde = list.ScanByPairs((prev, cur) => new { OrderNotOkay = prev.DateEntered >= cur.DateEntered, prev })
              .Where(op => op.OrderNotOkay)
              .Select(op => op.prev)
              .ToList();


(这给了我写WhereByPairs(及其许多同伴)的明显想法。)

如果您不想使用扩展方法,则可以使用LINQ Zip方法来模拟同一件事:

var ansde2 = list.Zip(list.Skip(1), (prev, cur) => new { OrderNotOkay = prev.DateEntered >= cur.DateEntered, prev })
                 .Where(op => op.OrderNotOkay)
                 .Select(op => op.prev)
                 .ToList();


而且,当然,您可以将订单测试封装在扩展方法中:

public static class ListDateExt {
    public static IEnumerable<T> OutOfOrder<T, TField>(this IEnumerable<T> src, Func<T,TField> selectorFn, Comparer<TField> cmp = null) {
        cmp = cmp ?? Comparer<TField>.Default;
        return src.ScanByPairs((prev, cur) => new { OrderNotOkay = cmp.Compare(selectorFn(prev), selectorFn(cur)) >= 0, prev })
                  .Where(op => op.OrderNotOkay)
                  .Select(op => op.prev);
    }
}


然后,您可以使用以下命令测试字段:

var ansdp = list.OutOfOrder(l => l.DatePaid).ToList();
var ansde = list.OutOfOrder(l => l.DateEntered).ToList();

关于c# - 如何在C#中使用LINQ查找乱序元素,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55872526/

10-11 15:59