如何检测IEnumerable <t>中的“缺失”元素?</t>

时间:2010-09-14 18:13:59

标签: c# linq ienumerable interpolation

我有一个IEnumerable<T>包含其中一个属性中具有一致间隔的数据元素列表:

List<Interval> list = new List<Interval>
            { 
                new Interval{ TIME_KEY = 600},
                new Interval{ TIME_KEY = 605},
                new Interval{ TIME_KEY = 615},
                new Interval{ TIME_KEY = 620},
                new Interval{ TIME_KEY = 630}
            };

如何查询此列表(最好使用Linq),以获得如下所示的列表:

 List<Interval> list = new List<Interval>
                { 
                    new Interval{ TIME_KEY = 610},
                    new Interval{ TIME_KEY = 625}
                };

编辑:我可能知道间隔距离应该是什么,但是如果有办法通过检查数据来确定它,那将是一个巨大的奖励!

编辑:更改为数值

6 个答案:

答案 0 :(得分:3)

一种有效而简单的方法就是通过foreach查看该列表并检测间隙 我认为5分钟的节拍是固定的吗?

要使用LINQ,您可以创建完整列表并找到差异,但这似乎有点过分。


考虑第2部分,确定间隔:

从您的示例中,可能会有3或4个值的样本。但是,即使在检查了所有值后,您也无法绝对确定。您的示例数据不会排除具有大量缺失值的1分钟频率。

因此,您需要有关此部分的非常好的规格。

答案 1 :(得分:3)

查看this question选择连续值的扩展方法。从那里,你可以做类似的事情:

// I'd probably rename SelectBetween to SelectConsecutive
list.SelectConsecutive((x, y) => new { Original = x, Interval = y - x})
    .Where(pair => pair.Interval != 5)
    .Select(pair => new Interval(pair.Original + 5));

(有些伪代码,但我希望你能看到我要去的地方。)

但是,只有当它丢失时才会生成一个元素...如果从0到20,它将不会生成5,10,15。

在Henk的第二个建议上添加一些东西:

var missing = Enumerable.Range(0, expectedElementCount)
                        .Select(x => new Interval(baseInterval + 5 * x)
                        .Except(list);

答案 2 :(得分:3)

var newList = 
     Enumerable.Range(0, 6)
               .Select(r=> new Interval() {TIME_KEY = ((r*5)+600) })
               .Except(list )

答案 3 :(得分:2)

如果您知道间隔,如果您有权访问Zip方法(.NET 4附带),这将有效:

list.Zip(list.Skip(1), (x,y) => new { x, delta = y - x })
    .SelectMany(a => Enumerable.Range(1, a.delta/interval - 1)
                               .Select(i => a.x + i*interval));

请注意,这会迭代列表两次,因此如果源是惰性可枚举的,则需要先缓冲它。使用ZipSkip的构造是将连续元素一起投影的快速而肮脏的方式。 Reactive Extensions'System.Interactive库有Scan方法,Jon在another answer中展示了可能的实现方式。这些都没有两次迭代列表,所以它们将是一个更好的选择。

如果要确定间隔,您可以获得最小增量:

var deltas = list.Zip(list.Skip(1), (x,y) => y - x );
var interval = deltas.Min();
list.Zip(deltas, (x, delta) => new { x, delta })
    .SelectMany(a => Enumerable.Range(1, a.delta/interval - 1)
                               .Select(i => a.x + i*interval));

我做了一些假设:

  • 元素之间的所有差异都是区间的倍数;
  • 输入已排序。

工作原理:

  1. 首先,它构建了一系列与每个元素的对,但是最后一个元素和它后面元素的间隔;
  2. 然后,对于这些对中的每一对,它会生成增量中的所有缺失值:在每个增量内都有精确的a.delta/interval - 1值,并且每个值都是远离元素存储区的一定数量的间隔。对,因此a.x + i*interval
  3. SelectMany负责将所有缺失值序列拼凑成一个。

答案 4 :(得分:0)

试试这个:

private static IEnumerable<Interval> CalculateMissingIntervals(IEnumerable<Interval> list, int step) {
  return list.Zip(list.Skip(1), 
    (i1, i2) => IntervalRange(i1.TIME_KEY + step, i2.TIME_KEY, step)).
    SelectMany(x => x);
}
private static IEnumerable<Interval> IntervalRange(int start, int end, int step) {
  for (var i = start; i < end; i += step) {
    yield return new Interval { TIME_KEY = i };
  }
}

假设初始列表已排序。

答案 5 :(得分:0)

IEnumerable<Interval> missingIntervals =
    Enumerable.Range(list.Min(listMember => listMember.TIME_KEY), list.Max(listMember => listMember.TIME_KEY))
              .Where(enumMember => enumMember % intervalDistance == 0)
              .Select(enumMember => new Interval { TIME_KEY = enumMember}
              .Except(list);