【问题标题】:Linq fast intersect query - enhancement?Linq 快速相交查询 - 增强?
【发布时间】:2014-12-25 02:51:28
【问题描述】:

我们公司有数千辆(!)汽车。每辆车都有一个 GPS 设备,它会定期 (cycle) 发送其位置。

所以每个Cycle 都包含:

  • List<Cars>(发送位置的汽车——对应CycleNum
  • CycleNum 循环编号

CycleNum服务器确定。

例如在 CycleNum=1 中,有 4 辆汽车发送了他们的位置:

我使用的类(简化)

static int TotalCycles=0;

class Car
{
 public int CarId;
 public int Location ;
}


class Cycle
{
  public  int CycleNum;
  public List<Car> Cars;
  public Cycle ()
    {
      CycleNum=(++TotalCycles);
    }
     
}

让我们填写一些数据:

   List<Cycle> LstCyclces = new List<Cycle>();
   Cycle cycle =null;

   cycle = new Cycle();//cycle 1
   cycle.Cars = new List<Car>();
   cycle.Cars.Add(new Car {CarId=1 , Location=40});
   cycle.Cars.Add(new Car {CarId=2 , Location=21});
   cycle.Cars.Add(new Car {CarId=3 , Location=5});
   cycle.Cars.Add(new Car {CarId=4 , Location=15});
   LstCyclces.Add(cycle);
   
   cycle = new Cycle();//cycle2
   cycle.Cars = new List<Car>();
   cycle.Cars.Add(new Car {CarId=1 , Location=40}); //same location
   cycle.Cars.Add(new Car {CarId=2 , Location=57});//changed location
   cycle.Cars.Add(new Car {CarId=3 , Location=100});//changed location
   cycle.Cars.Add(new Car {CarId=4 , Location=7});//changed location
   cycle.Cars.Add(new Car {CarId=7 , Location=2});//new attended ( vs previous cycle)
   LstCyclces.Add(cycle);
   
   cycle = new Cycle();//cycle3
   cycle.Cars = new List<Car>();
   cycle.Cars.Add(new Car {CarId=1 , Location=40}); //same
   cycle.Cars.Add(new Car {CarId=2 , Location=5});//changed Location
   cycle.Cars.Add(new Car {CarId=4 , Location=1});//changed Location
   cycle.Cars.Add(new Car {CarId=9 , Location=7});//new attended ( vs previous cycle)
   LstCyclces.Add(cycle);

可视化:

如你所见:

  • 一辆新车可以进入循环
  • 汽车也可以从自行车上下来
  • 一辆车可以改变位置(显然)

问题

我被要求:

对于特定的给定循环编号 - 查找在上一个循环中也预期的所有汽车其中:

("new Location" - "previous Location") &lt; abs(40)

然后从 该结果集 中,找到所有汽车 PAIRS 其中:

(Car_A.Location - Car_B.Location) &lt; abs(65)

简而言之 - 我需要所有在上一个周期也为我提供信息的汽车,而且它们距离之前的位置并没有很远,最后 - 从那些汽车 - 我需要知道哪些汽车彼此靠近.

非常重要:我不能只检查当前位置,因为我们还需要确保汽车没有离之前的位置很远。

所以根据图片:看着cycleNum=2

在前一个周期 (1) 中也预期的汽车是 Cars:1,2,3,4。

从该结果来看:离之前位置不远的汽车:

("new Location" - "previous Location") &lt; abs(40)

是汽车:1,2,4。

根据该结果,我需要找到现在彼此相距不远的所有汽车对:

(Car_A.Location - Car_B.Location) &lt; abs(65)

所以结果应该是 IEnumerable:(格式无关紧要)

  • { Car1 , Car2 , distance=17} //这两辆车之间的距离
  • { Car1 , Car4 , distance=33} //这两辆车之间的距离
  • { Car2 , Car4 , distance=50} //这两辆车之间的距离

//我不介意所有排列( {car1 car2} , {car2 car1} )

我尝试了什么:

   var cycleToCheck=2;
   //get all cars from desired cycle
   var requestedCycleCars =  LstCyclces.Where(c=>c.CycleNum==cycleToCheck).SelectMany(c=>c.Cars);
   //get all cars from previous  cycle
   var previousCycleCars =  LstCyclces.Where(c=>c.CycleNum==cycleToCheck-1).SelectMany(c=>c.Cars);
   //intersec between those 
   var MyWrongIntersect =requestedCycleCars.Intersect(previousCycleCars,new MyEqualityComparer());

但我只从当前周期而不是从前一个周期获取汽车,另外 - 我需要参考当前周期和前一个周期的汽车(无需重复) - 用于计算。

另外我认为我使用SelectMany 走错了路,这应该是最快的(c#,plinq?)。我希望它可以在一个查询中。

有什么帮助吗?

Full code working online

nb ,我当然可以分阶段进行,但重复或 ToList() 对我来说是不好的方法。我希望有一个 plinq 查询

编辑

发布的解决方案在逻辑上工作正常,但性能不佳。

2 个周期,每个周期有 10,000 辆汽车:> 9 分钟!!! :

http://i.stack.imgur.com/mjLvG.jpg

我怎样才能改进它?(asparallel 没多大用)

【问题讨论】:

  • 你考虑过排序吗?简单的排序可以减少您实际必须进行的实际比较的次数。目前(没有真正考虑过)我相信你的算法有O(n^2)
  • @Aron 你说的是&amp;&amp; x.Item1.CarId &lt; x.Item2.CarId 部分吗?
  • 不,我的意思是(Car_A.Location - Car_B.Location) &lt; abs(65) 部分。如果您按位置对汽车进行分类,那么显然第一辆车和最后一辆车不会位于相似的位置。尝试以一种可以快速查询的形式存储您的数据。这是数据库的基础。

标签: c# .net linq plinq


【解决方案1】:

嗯,就效率而言,

From that result I need to find all pairs of car who are now not far from each other : 是性能的杀手,假设有很多这样的对。天真的算法至少会运行n^2。您可能希望使用 SQL 空间类型,这将使​​查询更加高效。

如果你不愿意做/做不到,那么你可以做的事情不多,我愿意猜测。

这是一个在Cars 之间进行有效连接的代码。 CarId 被索引很重要。在我们找到所有对c.Distance &lt;40后,我们将在客户的计算机上进行最后的处理,因为这样我们可以高效地自行处理分拣的汽车。

var cycleNum = 2;

var curCycleCars = LstCyclces[cycleNum - 1].Cars;
var prevCycleCars = LstCyclces[cycleNum - 2].Cars;

var cars = curCycleCars.Join(prevCycleCars, 
                    p => p.CarId, 
                    y => y.CarId,
                    (f1, f2) => new { 
                            Car = f1,
                            Distance = f1.Location - f2.Location
                        })
                    .Where(c => c.Distance < 40)
                    .Select(c => c.Car)
                    .OrderBy(car => car.Location)
                    .ToList();



var carPairs = new CarPairList[cars.Count()];

for(var i = 0; i < cars.Count; i++)
{
    var curCar = cars[i];
    var curStartIndex = i + 1;

    if(i > 0)
    {
        var previousCarPair = carPairs[i - 1];
        if(previousCarPair!=null)
        {
            curStartIndex = previousCarPair.EndIndex;
        }
    }

    int j;
    for(j = curStartIndex; j < cars.Count; j++)
    {
        var dis = cars[j].Location - curCar.Location;
        if(dis >= 65) break;
    }

    var startIndex = i + 1;
    var endIndex = j - 1;

    if(endIndex >= startIndex)
    {
        carPairs[i] = new CarPairList(curCar, 
                            startIndex, endIndex);
    }
}

foreach(var carPair in carPairs.Where(x => x!=null)){
    Console.WriteLine("Car " + carPair.Car.CarId);
    Console.WriteLine("Cars near the distance: ");

    for(var i = carPair.StartIndex; i <= carPair.EndIndex; i++){
        Console.WriteLine("\t - {0}, distance {1}", 
            cars[i].CarId,
            cars[i].Location - carPair.Car.Location);
    }

    Console.WriteLine();
}

class CarPairList
{
    public readonly Car Car;
    public readonly int StartIndex;
    public readonly int EndIndex;

    public CarPairList(Car car, 
        int startIndex,
        int endIndex){
        Car = car;
        StartIndex = startIndex;
        EndIndex = endIndex;
    }
}

【讨论】:

  • 没有 sql 。全部在内存中处理:-)(我们有很多)
  • 奇怪的是,没有一个答案使用 plinq 来使最大内核变热?
  • @RoyiNamir,没有太多可以并行化 imo。数据是依赖的。您可以尝试在 ToList() 之前添加.AsParallel()。顺便说一句,我还改变了找到汽车对的方式。内存分配现在是最小的,应该会快很多。
  • & 另外,如果没有 PRINTING 部分,上述算法在大约 10 000 辆汽车的情况下最多需要 1 秒,因为运行时间是 O(nlogn)。如果它需要更多,那么你在其他地方会出错。
  • 我只是在尝试(粘贴到 vs)
【解决方案2】:

Tty 这段代码

    var cycleToCheck = 2;

    var query = LstCyclces.FirstOrDefault(c => c.CycleNum == cycleToCheck).Cars
                                .Where(c => LstCyclces.FirstOrDefault(p => p.CycleNum == cycleToCheck - 1).Cars
                                            .Any(ca => ca.CarId == c.CarId && Math.Abs(c.Location - ca.Location) < 40));

    var result = query.SelectMany(t1 => query.Select(t2 => Tuple.Create(t1, t2)))
            .Where(x => Math.Abs(x.Item1.Location - x.Item2.Location) < 65 && x.Item1.CarId < x.Item2.CarId);


    foreach (var r in result)
    {           
        Console.WriteLine("{0} - {1}", r.Item1.CarId, r.Item2.CarId);
    }

Here 是工作示例

已编辑

【讨论】:

  • +1 谢谢。但是我试图避免(如我所说) Tolists() 和中间缓冲区,并希望使用 plinq ,因为我告诉过,我们有数千(!)辆汽车(这是我给出的非常简化的代码,实际上是类有点大)。
  • query.SelectMany(t1 =&gt; query.Select(t2 =&gt; Tuple.Create(t1, t2))); 很巧妙。我还在想它是如何工作的。 (组合生成器)
  • 实际上在SQL中转化为CROSS JOIN
  • 你能看看我的编辑吗?关于执行时间?
  • @MaxBrodin 您使用 Tuple.Create 而不是 new { MeaningfulPropertyName = t1, OtherMeaningfulPropertyName = t2 } 有什么原因吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-11
  • 2017-11-08
  • 2016-07-07
  • 1970-01-01
  • 2013-09-20
相关资源
最近更新 更多