按矢量中的距离对矢量值进行排序

Sorting vector values by Distance in vector

本文关键字:排序 距离      更新时间:2023-10-16

我有两个位置,我的玩家位置(2D)和我在一个实体向量上循环,每个向量都有一个位置(2D

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

感谢您的帮助!

因为你是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函数直接传递给另一个函数调用(无需首先定义具有特定名称的函数,因为它是一个匿名函数)。这个函数调用是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);
}
);
}

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

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

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

//...
[&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函数中捕获变量的信息,请阅读此处或在谷歌上搜索"C++11 lambda捕获语法"。

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

通过跟踪离你最近的元素的索引,这可能更容易做到,这在像这样的"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++了,我不知道这是否是做平方根和幂的正确方法。然而,它的好处是只跟踪double和int,而不是对象。

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

...
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是数字,closestEntEntity,不知道你的类型。

Rember将smallestDist初始化为所选数字类型的最大可能数字。例如long smallestDist = 0xffffffff;

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