【问题标题】:How do I delete the closest "Point" object in a STD::List to some x,y?如何删除 STD::List 中与某个 x,y 最接近的“点”对象?
【发布时间】:2009-02-11 20:49:47
【问题描述】:

我有一个点类:

class Point {
public:
    int x, y;
    Point(int x1, int y1)
    {
        x = x1;
        y = y1;
    }
};

还有一个点列表:

std::list <Point> pointList;
std::list <Point>::iterator iter;

我正在将积分推送到我的 pointList(尽管如果尚未推送任何积分,列表可能还没有包含任何积分)。

我有两个问题:

如何从列表中删除到任意 (x, y) 的最近点?

假设我有 x,y (5,12),我想在列表中找到最接近该点的点并将其从 STD::List 中删除。

我知道我必须使用距离公式,并且我必须使用迭代器遍历列表,但是我在概念化如何在迭代时跟踪哪个点最接近时遇到了一些麻烦通过列表。

如何返回给定 (x,y) 的 x 半径内的点的数组或列表?

与上一个问题类似,除了我需要一个指向给定 (x,y) 半径范围内的“点”对象的指针列表。另外,我应该返回一个数组还是一个列表?

如果有人可以帮助我,我仍在努力学习 C++,我很感激。

【问题讨论】:

  • @Nathan Fellman,我不在学校。我正在尝试学习 C++,我决定制作一个绘图程序。所以,我猜是自己做作业。

标签: c++ list pointers distance


【解决方案1】:

在循环遍历列表时,使用 std::list::iterator 变量来跟踪最近点。当您到达列表末尾时,它将包含最近的点,可用于擦除该项目。

void erase_closest_point(const list<Point>& pointList, const Point& point)
{
    if (!pointList.empty())
    {
        list<Point>::iterator closestPoint = pointList.begin();
        float closestDistance = sqrt(pow(point.x - closestPoint->x, 2) +
                                     pow(point.y - closestPoint->y, 2));

        // for each point in the list
        for (list<Point>::iterator it = closestPoint + 1;
             it != pointList.end(); ++it)
        {
            const float distance = sqrt(pow(point.x - it->x, 2) +
                                        pow(point.y - it->y, 2));

            // is the point closer than the previous best?
            if (distance < closestDistance)
            {
                // replace it as the new best
                closestPoint = it;
                closestDistance = distance
            }
        }

        pointList.erase(closestPoint);
    }
}

在给定点的半径内构建点列表是类似的。请注意,空半径列表通过引用传递给函数。通过引用将点添加到列表中将消除在按值返回向量时复制所有点的需要。

void find_points_within_radius(vector<Point>& radiusListOutput,
                               const list<Point>& pointList,
                               const Point& center, float radius)
{
    // for each point in the list
    for (list<Point>::iterator it = pointList.begin();
         it != pointList.end(); ++it)
    {
        const float distance = sqrt(pow(center.x - it->x, 2) +
                                    pow(center.y - it->y, 2));

        // if the distance from the point is within the radius
        if (distance > radius)
        {
            // add the point to the new list
            radiusListOutput.push_back(*it);
        }
    }
}

再次使用复制如果:

struct RadiusChecker {
    RadiusChecker(const Point& center, float radius)
        : center_(center), radius_(radius) {}

    bool operator()(const Point& p)
    {
        const float distance = sqrt(pow(center_.x - p.x, 2) +
                                    pow(center_.y - p.y, 2));
        return distance < radius_;
    }

private:
    const Point& center_;
    float radius_;
};

void find_points_within_radius(vector<Point>& radiusListOutput,
                               const list<Point>& pointList,
                               const Point& center, float radius)
{
    radiusListOutput.reserve(pointList.size());
    remove_copy_if(pointList.begin(), pointList.end(),
                   radiusListOutput.begin(),
                   RadiusChecker(center, radius));
}

请注意,如果您需要额外的性能,可以删除 sqrt,因为幅度的平方同样适用于这些比较。此外,如果您真的想提高性能而不是考虑像quadtree 这样允许场景分区的数据结构。第一个问题与collision detection 密切相关,关于该主题有大量有价值的信息可用。

【讨论】:

  • 谢谢你给我看这个。我有一个简单的问题,如果 pointList 为空怎么办,当我们在第一个示例中尝试获取 pointList.begin() 时会不会遇到问题?我不能确保它会有意义
  • it!=pointList.end() 检查处理这种情况。您可以将 pointList.end() 视为过去最后一项,因此如果没有任何项,则 pointList.begin()==pointList.end()
  • @KingNestor:在获取该代码之前,您需要确保列表不为空。否则,第二行在尝试访问 NULL 迭代器时会出现段错误。
  • 使用 std::copy_if 和 back_inserter 可能会更短。
  • 你是这个意思吗?它不是更短,但由于编译器内联,它可能会更快。
【解决方案2】:

你对它应该如何制作是正确的。只需遍历列表中的所有项目并跟踪已经找到的最小距离,以及您在两个变量中找到的最近点,如果问题如此,请确保您不将点与自身匹配。然后删除你找到的点。

这究竟是如何制作的,留作练习。

如果您想从另一个点获取给定半径内的点列表,请迭代该列表并构建第二个列表,该列表仅包含指定范围内的点。

同样,它是如何在代码中生成的,留给您作为练习。

【讨论】:

    【解决方案3】:

    您可以使用 STL 和 Boost.IteratorsBoost.Bind 的组合来执行此操作 - 为了您的方便,我将在此处粘贴您的问题的解决方案的全部来源:

    #include <list>
    #include <cmath>
    #include <boost/iterator/transform_iterator.hpp>
    #include <boost/bind.hpp>
    #include <cassert>
    
    using namespace std;
    using namespace boost;
    
    struct Point {
        int x, y;
        Point() : x(0), y(0) {}
        Point(int x1, int y1) : x(x1), y(y1) {}
        Point(Point const & other) : x(other.x), y(other.y) {}
        Point & operator=(Point rhs) { rhs.swap(*this); return *this; }
        void swap(Point & other) { std::swap(other.x, x); std::swap(other.y, y); }
    };
    
    double point_distance(Point const & first, Point const & second) {
        double x1 = first.x;
        double x2 = second.x;
        double y1 = first.y;
        double y2 = second.y;
        return sqrt( ((x2 - x1) * (x2 -x1)) + ((y2 - y1) * (y2 - y1)) );
    }
    
    int main(int argc, char * argv[]) {
        list<Point> points;
        points.push_back(Point(1, 1));
        points.push_back(Point(2, 2));
        points.push_back(Point(3, 3));
        Point source(0, 0);
        list<Point>::const_iterator closest = 
            min_element(
                    make_transform_iterator(
                        points.begin(),
                        bind(point_distance, source, _1)
                        ),
                    make_transform_iterator(
                        points.end(),
                        bind(point_distance, source, _1)
                        )
                    ).base();
        assert(closest == points.begin());
        return 0;
    }
    

    解决方案的核心是使用变换迭代器使用point_distance 函数变换列表中的每个元素,然后从所有距离中获取最小距离。您可以在遍历列表时执行此操作,最后到达transform_iterator 以获取基本迭代器(使用base() 成员函数)。

    现在您有了该迭代器,您可以将 assert(closest == points.begin()) 替换为 points.erase(closest)

    【讨论】:

    • 我真的很喜欢这个答案。在我自己的回答中,我试图做一些类似的事情,但肯定我必须研究更多的变换迭代器。
    【解决方案4】:

    我同意之前的解决方案,只是想补充一点。尽管您的 Point 类不是很大,因此副本并不是真正的问题,但您可以考虑将 Point* 用于您的列表。这样,当您创建第二个列表时,您将存储指向同一类的指针。这样做的不利方面是,如果您从多个列表中删除而没有管理所有创建点的“主”,则如果您没有删除基础类或意外删除仍然存在的类,则可能会造成内存泄漏在另一个列表中使用。不过,需要考虑的事情取决于您的系统如何发展。

    【讨论】:

      【解决方案5】:

      你必须保留迭代器才能删除它。

      std::list<Point>::iterator closest;
      std::list<Point>::iterator it = pointList.begin();
      double min_dist=dist(your_point, *it);
      ++it;
      for (; it != pointList.end(); ++it)
      {
              double actual_dist = dist(your_point, *it);
              if (actual_dist < min_dist)
              {
                      min_dist = actual_dist;
                      closest = it;
              }
      }
      
      pointList.erase(closest);
      

      【讨论】:

      • 这可能不会删除最近的点,因为您正在使用整数算术有效地获取距离的地板。
      • 例如,当与 (0,0) 进行比较时,(4,4) 和 (5,1) 都会返回 5 的距离,但其中一个比另一个更近。
      • 哎呀,对不起。我应该为 min_dist 放一个浮点数或双精度数。谢谢你的提示。我重新编辑它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多