5

我有两个位置,我的玩家位置(2D),我正在循环一个实体向量,每个实体都有一个位置(2D),我希望我的函数返回到我的位置的最近距离,但我不知道如何这样做是因为我被卡住了,在 c# 中我可以只使用 linq,但我正在学习 c++ 我的函数如下所示:

const Entity GetNearestEntity()
{
    for (Entity i : fb.Entities)
    {
        double xDiff = (double)abs(fb.PlayerLocation.X - i.X);
        double yDiff = (double)abs(fb.PlayerLocation.Y - i.Y);

        //Stuck here.. 
        //How can i get the closest object out of my vector<Entity> collection?
    }
}

帮助表示赞赏!

4

4 回答 4

12

由于您是 C++ 新手(正如您所说),我想向您展示一个不错的替代方案,它需要一些不同的想法,但是一旦您习惯了它,您就会喜欢它。

基本思想

您正在寻找一个元素(或者您希望对该元素的引用可能稍后对其进行修改),它是相对于某种比较方法的最小元素。C++ 允许您选择如何比较元素,分别针对特定查询(在这种情况下,不要将比较视为定义明确的“小于”/“大于”运算符,但这是一个类似的概念)。

您可以为这个特定场景在本地定义这样的比较。比较方法可以实现为独立函数、仿函数(实现所谓的“调用运算符”的函数对象)或 lambda 函数,您应该更喜欢它们。

Lambda 函数语法

Lambda 函数是匿名函数,通常在您按字面意思编写它们的地方使用。lambda 函数语法是您必须习惯的东西,但是一旦您习惯了,它就是一个强大的东西!

[](Entity a, Entity b) {  return a.X < b.X;  }

这是一个 lambda 函数,它接受两个Entity实例并简单地比较它们的 X 坐标。当然这不是你想要的,但我想先向你展示语法。

现在我们要实现一个比较函数,它比较相对于原点(你的玩家位置)的坐标,所以这是从函数外部传递的东西,而不是作为参数(因为比较函数只能接受两个值进行比较)。这是通过捕获一些上下文变量来完成的。看看这里:

int captured = ...
...
[captured](Entity a, Entity b) {  return a.X + captured < b.X + captured;  }

仍然没有任何意义,它展示了如何从 lambda 函数内部引用在 lambda 函数外部定义的变量。

针对您的特定问题的 Lambda 函数

现在我们可以正确编写比较方法了:

[fb](Entity a, Entity b) {
    double ax = fb.PlayerLocation.X - a.X;
    double ay = fb.PlayerLocation.Y - a.Y;
    double a = ax * ax + ay * ay; // we can save the sqrt()
    double bx = fb.PlayerLocation.X - b.X;
    double by = fb.PlayerLocation.Y - b.Y;
    double b = bx * bx + by * by; // we can save the sqrt()
    return a < b;
}

所以我们捕获fb,我们计算两个实体的相对坐标ab计算长度的平方(这样我们可以保存 sqrt)并比较这些距离。代码看起来有点臃肿;我们稍后会对此进行改进。

在 STL 算法中使用 Lambda 函数

一旦你了解了如何编写这样的比较函数,最后一步就变得微不足道了,因为STL 提供了许多可以使用的算法,可以使用这样的函数:

Entity closest = *std::min_element(fb.Entities.begin(), fb.Entities.end(),
    [fb](Entity a, Entity b) {
        double ax = fb.PlayerLocation.X - a.X;
        double ay = fb.PlayerLocation.Y - a.Y;
        double a = ax * ax + ay * ay; // we can save the sqrt()
        double bx = fb.PlayerLocation.X - b.X;
        double by = fb.PlayerLocation.Y - b.Y;
        double b = bx * bx + by * by; // we can save the sqrt()
        return a < b;
    }
);

如您所见,我只是将 lambda 函数直接传递给另一个函数调用(没有先定义具有特定名称的函数,因为它是一个匿名函数)。此函数调用 is std::min_element,它查找两个迭代器之间的最小元素(如果要在整个容器中搜索,请使用开始/结束迭代器对)。它返回另一个迭代器。使用*前缀运算符,您可以访问迭代器指向的元素。

请注意,一旦将其存储为Entity值,它就会被复制,因此修改不再直接写入向量,而是写入本地副本。为避免这种情况,请使用Entity&,它是对向量中元素的(可修改)引用,您的函数可以毫无问题地返回它(只要引用的值在您的函数之外有效,在您的情况下就是这样)。

改进

如果您编写一个比较两个实体的距离函数,这会变得更简单:(注意缺少abs,这是因为我们无论如何都要对值进行平方,任何负号都会消失)

double distance(Entity p, Entity q) {
    double delta_x = p.X - q.X;
    double delta_y = p.Y - q.Y;
    return sqrt(delta_x * delta_x + delta_y * delta_y);
}

或者,再次保存 sqrt:

double distanceSquare(Entity p, Entity q) {
    double delta_x = p.X - q.X;
    double delta_y = p.Y - q.Y;
    return (delta_x * delta_x + delta_y * delta_y);
}

所以代码变成了:

Entity closest = *std::min_element(fb.Entities.begin(), fb.Entities.end(),
    [fb](Entity a, Entity b) {
        return distanceSquare(a, fb.PlayerLocation) <
               distanceSquare(b, fb.PlayerLocation);
    }
);

另一个改进是通过(不可修改的)引用传递变量,而不是通过值传递它们。这意味着不需要复制变量。将代码放在您在问题中编写的方法中,代码变为(应用引用调用概念并返回可修改引用):

double distanceSquare(const Entity & p, const Entity & q) {
    double delta_x = p.X - q.X;
    double delta_y = p.Y - q.Y;
    return (delta_x * delta_x + delta_y * delta_y);
}

Entity & GetNearestEntity()
{
    return *std::min_element(fb.Entities.begin(), fb.Entities.end(),
        [fb](const Entity & a, const Entity & b) {
            return distanceSquare(a, fb.PlayerLocation) <
                   distanceSquare(b, fb.PlayerLocation);
        }
    );
}

(注意嵌套的 return 语句。内层是 lambda 函数的一部分,返回比较逻辑的结果。外层返回最终结果,因此是距离最小的实体。)

最后,它看起来更干净(至少在我看来)。一旦你理解了这个概念,你就会看到它的美。:)

Andrew Durward 对这个答案的评论中提到了我将向您展示的最后一个改进:我们现在编写的 Lambda 函数fb为每次调用复制一次值,GetNearestEntity因为它可能自上次调用以来发生了变化。我们可以通过引用捕获来避免这种复制操作,这与引用调用的概念相同,但针对捕获的变量。只需&在捕获表达式中的变量名前面写:

//...
        [&fb](const Entity & a, const Entity & b) {
            return distanceSquare(a, fb.PlayerLocation) <
                   distanceSquare(b, fb.PlayerLocation);
        }
//...

捕获语法一开始可能看起来有点奇怪,但它提供了强大的控制,可以控制通过引用捕获封闭上下文中的哪些变量:

来源:http ://www.cprogramming.com/c++11/c++11-lambda-closures.html

[]          Capture nothing (or, a scorched earth strategy?)
[&]         Capture any referenced variable by reference
[=]         Capture any referenced variable by making a copy
[=, &foo]   Capture any referenced variable by making a copy, but capture variable foo by reference
[bar]       Capture bar by making a copy; don't copy anything else
[this]      Capture the this pointer of the enclosing class

如果您想了解更多关于在 lambda 函数中捕获变量的信息,请在此处阅读或在google 上阅读“C++11 lambda capture syntax”

此代码的演示:http: //ideone.com/vKAFmx

于 2012-12-22T02:12:57.157 回答
4

通过跟踪离您最近的元素的索引可能更容易做到这一点,这在“for”循环中会更容易,例如:

Entity GetNearestEntity()
{
    int closestEnt = 0;
    double smallestDist = -1.0;

    for (int i = 0; i < Entities.length(); i++) 
    {
        double xDiff = (double)abs(fb.PlayerLocation.X - Entities[i].X);
        double yDiff = (double)abs(fb.PlayerLocation.Y - Entities[i].Y);
        double totalDist = sqrt(pow(xDiff, 2) + pow(yDiff, 2));

        if ((totalDist < smallestDist) || (smallestDist == -1.0))
        {
            closestEnt = i;
            smallestDist = totalDist;
        }
    }

    return Entities[closestEnt];
}

这可能无法立即编译,自从我使用 C++ 以来已经有一段时间了,我对这是否是计算平方根和幂的正确方法一无所知。然而,它有一个很好的好处,就是只跟踪一个双精度数和一个整数,而不是一个对象。

于 2012-12-22T01:40:07.383 回答
3

您需要跟踪具有最小平方加法的实体。像这样的东西:

...
for (Entity i : fb.Entities)
{
    double xDiff = (double)abs(fb.PlayerLocation.X - i.X);
    double yDiff = (double)abs(fb.PlayerLocation.Y - i.Y);

    // this is not the actual distance, for that you would need sqrt(). But
    // it is enough to know if it is bigger or smaller.
    dist = xDiff*xDiff + yDiff*yDiff;
    closestEnt = ( dist < smallestDist ? i : closestEnt );
    smallestDist = ( dist < smallestDist ? dist : smallestDist );
}
...

在哪里distsmallestDist是数字和closestEnt一个Entity,真的不知道你的类型。

请记住初始化smallestDist为您选择的数字类型的最大可能数字。例如long smallestDist = 0xffffffff;

于 2012-12-22T01:31:18.937 回答
0
int GetNearestEntity()
{
    int closestEnt = 0;
    int smallestDist = -1;
    int i = 0;

    if (fb.Entities.size() > 0)
    {
        for (Entity entity : fb.Entities) 
        {
           double xDiff = (double)abs(fb.PlayerLocation.X - entity.X);
           double yDiff = (double)abs(fb.PlayerLocation.Y - entity.Y);
           double totalDist = sqrt(pow(xDiff,2) + pow(yDiff,2));

           if ( (totalDist < smallestDist) || (smallestDist = -1))
           {
                closestEnt = i;
                smallestDist = totalDist;
           }

           i++;
        }
        return closestEnt;
    }
    return 0;
}
于 2012-12-22T02:02:14.633 回答