代码之家  ›  专栏  ›  技术社区  ›  Chris McCall

如何检测IEnumerable中的“缺少”元素?

  •  2
  • Chris McCall  · 技术社区  · 14 年前

    我有一个 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 回复  |  直到 14 年前
        1
  •  3
  •   Community Lee Campbell    7 年前

    看看 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。

    把肉放在亨克的第二个建议上:

    var missing = Enumerable.Range(0, expectedElementCount)
                            .Select(x => new Interval(baseInterval + 5 * x)
                            .Except(list);
    
        2
  •  3
  •   James Curran    14 年前
    var newList = 
         Enumerable.Range(0, 6)
                   .Select(r=> new Interval() {TIME_KEY = ((r*5)+600) })
                   .Except(list )
    
        3
  •  3
  •   Henk Holterman    14 年前

    一个有效而简单的方法就是通过 foreach 并检测间隙。
    我想5分钟的战术是固定的?

    要使用LINQ,您可以创建完整的列表并找出不同之处,但这似乎有些过分。


    考虑到第二部分,确定间隔:

    从您的示例中,3或4个值的示例可能会这样做。但即使在检查之后你也不能完全确定 全部的 价值观。示例数据不排除1分钟频率和大量缺失值。

    所以你需要很好的规格说明。

        4
  •  2
  •   Community Lee Campbell    7 年前

    如果知道时间间隔,如果您可以访问 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));
    

    请注意,这将对列表进行两次迭代,以便在源代码是惰性可枚举的情况下,您需要首先对其进行缓冲。那个建筑 Zip Skip 是将连续元素投影到一起的快速而肮脏的方法。反应性扩展' 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. 然后,对于这些对中的每一个,它在delta中生成所有缺失的值:在每个delta中 a.delta/interval - 1 值,每个值都是一个与元素存储在对中的间隔数,因此 a.x + i*interval .
    3. SelectMany 注意将所有丢失值的序列展平为一个。
        5
  •  0
  •   Jordão    14 年前

    试试这个:

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

    假设初始列表已排序。

        6
  •  0
  •   Jimmy Hoffa    14 年前
    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);