C# - 加速使用嵌套for循环的List比较

时间:2017-07-13 10:36:51

标签: c# algorithm list object for-loop

我目前正在开发一个测试程序,确保主板上的孔彼此不太靠近或者它们不重叠。

为了做到这一点,我保留了所有的漏洞。名为holeInfo的对象中的X,Y坐标和半径以及列表holeInfoList中的对象。

我目前正在使用嵌套的for循环来遍历所有的孔和一个基本的数学公式来检查孔之间的距离。

这是我使用的功能:

public void checkHoleConditions()
{
    for (int i = 0; i < holeInfoList.Count - 1; i++)
    {
        errorType = new List<string>();

        for (int j = i + 1; j < holeInfoList.Count; j++)
        {
            holeMinSpaceFailed = false;

            if (failsHoleConditions(holeInfoList[i], holeInfoList[j])) 
            {
                if (holeMinSpaceFailed)
                {
                    errorType.Add("X: " + holeInfoList[j].holeXCoordinate + " Y: " + holeInfoList[j].holeYCoordinate + "R: " + holeInfoList[j].holeDiameter + " too close.");
                }
                else
                {
                    errorType.Add("X: " + holeInfoList[j].holeXCoordinate + " Y: " + holeInfoList[j].holeYCoordinate + "R: " + holeInfoList[j].holeDiameter + " overlap.");
                }

                invalidHole = new InvalidHole(holeInfoList[i], errorType);
                invalidHoleList.Add(invalidHole);
                Console.WriteLine("Hole Error Detected");
            }
            else
            {
                Console.WriteLine("Hole Check Successful");
            }
        }
    }
}

public bool failsHoleConditions(HoleInfo holeOne, HoleInfo holeTwo)
    {
        float holeOneXCoordinate = holeOne.holeXCoordinate;
        float holeOneYCoordinate = holeOne.holeYCoordinate;
        float holeOneRadius = holeOne.holeDiameter / 2;

        float holeTwoXCoordinate = holeTwo.holeXCoordinate;
        float holeTwoYCoordinate = holeTwo.holeYCoordinate;
        float holeTwoRadius = holeTwo.holeDiameter / 2;

        float holeXDifferenceSquared = (float)Math.Pow((holeOneXCoordinate - holeTwoXCoordinate), 2);
        float holeYDifferenceSquared = (float)Math.Pow((holeOneYCoordinate - holeTwoYCoordinate), 2);
        float distanceBetweenCenters = (float)Math.Sqrt(holeXDifferenceSquared + holeYDifferenceSquared);

        float distanceBetweenHoles = distanceBetweenCenters - (holeOneRadius + holeTwoRadius);

        if (distanceBetweenHoles <= 0)
        {
            return true;
        }
        else if (distanceBetweenHoles <= minimumSpace)
        {
            holeMinSpaceFailed = true;
            return true;
        }
        return false;
    }

目前,我的程序在大约2.5分钟内对254个列表对象进行测试。考虑到这是一个测试程序,2.5分钟只有254个孔是很长的时间。

为了让测试运行得更快,我可以实现哪些算法?

3 个答案:

答案 0 :(得分:3)

这个解决方案的灵感来自物理引擎,以及那里负责碰撞检测的优化。

我不会声称完全知道它是如何工作的,为了更好的解决方案,你应该尝试研究ODE或Bullet Dynamics。

基本上,解决方案是将对象(孔)分隔成岛,并且仅将每个对象的位置与同一岛中的对象进行比较。如果你无法想出一种正确分离岛屿的方法,你可以这样做:

假设我们在一个方形场上有125个物体,5乘5。你可以将它分成25个主要的方形岛,然后是8个交叉岛(长岛,沿着主岛的边缘。这些岛的最小边)应该是您想要计算的最小距离)。群岛可以重叠。您必须解析整个列表一次才能进行此拆分。这意味着到目前为止,我们总共遍历了125个项目 - O(n)

接下来,对于每个岛屿(总共33个,O(n^(2/3)),通过使用相同的嵌套循环找到比它们必须更近的对象。每个岛的总复杂度为O((n / n^(2/3))^2) = {{ 1}}。对岛屿数量进行计算,我们得出此算法的总复杂度= O(n^(2/3)),小于最初提出的O(n^(4/3))

希望这是有道理的。如果你愿意,我可以写一个Python演示。只是它会有很多代码。

修改
或者您可以使用2D物理引擎并将对象绘制为直径等于孔之间的最小距离的圆,然后让它检测碰撞。或者从那里获取相关代码(如果许可证允许),因为整个物理引擎对于任务来说有点过分 https://github.com/VelcroPhysics/VelcroPhysics

编辑2:

  

254个列表对象

我以为你正在解析254个不同的电路板。我强调的这个解决方案只有在巨大的计算负荷下才有意义。

答案 1 :(得分:1)

如果failsHoleConditions为真,

holeMinSpaceFailed永远不会为真 不要传递像那样的逻辑

x * xMath.Pow

无需采用Sqrt - 只需使用square minimumSpace

像这样的东西。 运行100万300毫秒。您的代码需要几分钟才出错。

static byte HoleTooClose(HoleInfo x, HoleInfo y, float minDistance)
{
    float holeSize = (x.Diameter + y.Diameter) / 2;
    float deltaX = y.X - x.X;
    float deltaY = y.Y - x.Y;

    float distanceSquared = deltaX * deltaX + deltaY * deltaY - holeSize * holeSize;

    if (distanceSquared <= 0)
    {
        return 0;
    }

    float minDistanceSquared = minDistance * minDistance;
    if (distanceSquared <= minDistanceSquared)
    {
        return 1;
    }

    return 2;
}

internal struct HoleInfo
{
    public float Diameter { get; internal set; }
    public float X { get; internal set; }
    public float Y { get; internal set; }
    public HoleInfo (float x, float y, float diameter)
    {
        X = x;
        Y = y;
        Diameter = diameter;
    }
}


static bool DistanceTooClose(System.Windows.Point x, System.Windows.Point y, Double minDistance)
{
    double deltaX = Math.Abs(y.X - x.X);
    double deltaY = Math.Abs(y.Y - x.Y);
    double distanceSquared = deltaX * deltaX + deltaY * deltaY;
    //double distance = Math.Sqrt(distanceSquared);
    Double minDistanceSquared = minDistance * minDistance;
    return (distanceSquared <= minDistanceSquared);
}

答案 2 :(得分:0)

如果你需要对所有人进行全部测试,一个优化可以是将它们放在nxn矩阵中,而不是列表中。然后,并行执行验证。矩阵越大,核心越多越好。并行也可以在列表上执行,但我不能100%确定.Net是否会确定等待每个线程结束,因为算法是顺序的。

如果您的方法使用欧氏距离,您可以尝试根据此距离首先对列表进行排序。相对于0的sqr(x)+ sqr(y)可以是首先排序的分数。我认为.Net可以轻松处理这种排序。稍后,只需运行算法直到第一个允许的电路。然后,您知道其余部分必须被接受,因此您只会在列表的第一个元素上执行