【问题标题】:Best way to to average duplicate properties in C++ vector在 C++ 向量中平均重复属性的最佳方法
【发布时间】:2016-08-22 11:02:48
【问题描述】:

我有一个std::vector<PLY>,它包含许多结构:

struct PLY {
    int x;
    int y;
    int greyscale;
}

某些 PLY 的位置可能是重复的 xy,但不一定是它们的 greyscale 值。找到那些(位置)重复项并将其替换为单个 PLY 实例的最佳方法是什么,该实例的灰度值代表所有重复项的平均灰度?

例如:PLY a{1,1,188}PLY b{1,1,255} 的副本。相同的 (x,y) 位置可能不同的灰度。

【问题讨论】:

  • 如果它们是完全重复的,那么所有重复的平均值是什么意思?
  • 如果你可以修改向量,你可以sort它并删除consecutive duplicates
  • @bolov 我编辑了我的问题。 PLY 在位置 (x,y) 方面是完全重复的,但在灰度值方面则不一定。
  • 另请注意,如果它们的总和不能被 n 整除,则计算 n 个整数的平均值(并将其存储在一个整数中)将导致近似结果
  • @JoachimPileborg 是否可以让 Kevin 完成 O(n*n) 次复制操作?

标签: c++ algorithm vector


【解决方案1】:

根据您对Ply 的描述,您需要这些运算符:

auto operator==(const Ply& a, const Ply& b)
{
  return a.x == b.x && a.y == b.y;
}
auto operator<(const Ply& a, const Ply& b)
{
  // whenever you can be lazy!
  return std::make_pair(a.x, a.y) < std::make_pair(b.x, b.y);
}

非常重要:如果定义“如果 xy 相同,则两个 Ply 相同”不是一般有效的,那么定义忽略 greyscale 的比较器运算符是一个坏主意。在这种情况下,您应该定义单独的函数对象或非运算符函数并将它们传递给函数。

有一个很好的经验法则,一个函数不应该有超过一个循环。因此,我们定义了这个辅助函数,而不是嵌套的 2 个 for 循环,它计算连续重复的平均值并返回连续重复范围的结尾:

// prereq: [begin, end) has at least one element
//         i.e. begin != end
template <class It>
auto compute_average_duplicates(It begin, It end) -> std::pair<int, It>
   // (sadly not C++17) concepts:
   //requires requires(It i) { {*i} -> Ply; }
{
  auto it = begin + 1;
  int sum = begin->greyscale;
  for (; it != end && *begin == *it; ++it) {
    sum += it->greyscale;
  }
  // you might need rounding instead of truncation:
  return std::make_pair(sum / std::distance(begin, it), it);
}

有了这个,我们可以得到我们的算法:

auto foo()
{
  std::vector<Ply> v = {{1, 5, 10}, {2, 4, 6}, {1, 5, 2}};

  std::sort(std::begin(v), std::end(v));

  for (auto i = std::begin(v); i != std::end(v); ++i) { 
    decltype(i) j;
    int average;

    std::tie(average, j) = compute_average_duplicates(i, std::end(v));

    // C++17 (coming soon in a compiler near you):
    // auto [average, j] = compute_average_duplicates(i, std::end(v));

    if (i + 1 == j)
      continue;

    i->greyscale = average;
    v.erase(i + 1, j);
    // std::vector::erase Invalidates iterators and references
    // at or after the point of the erase
    // which means i remains valid, and `++i` (from the for) is correct
  }
}

【讨论】:

  • +1 但一件事;在这种情况下,我会避免重载 operator==operator&lt;;他们没有考虑greyscale。它可能会在以后给其他程序员甚至我们自己造成混淆。另外,如果我们想定义另一个考虑greyscale 的比较器,我们就搞砸了。
  • @Ohashi 我考虑过这一点,但 OP 说根据定义,如果两个 Ply 相同,则它们的 xy 是相同的。如果这不是一个普遍有效的命题,那么应该使用非运算符比较器来代替。
  • 这是 n^2 不是吗? ...不,只是 n lg n。由于您的擦除,无需等待 n^2。擦除可以被修复,就像删除然后擦除尾巴一样。
【解决方案2】:

您可以先应用字典排序。在排序期间,您应该注意溢出的greyscale。使用目前的方法,你会有一些舍入误差,但它会很小,因为我先求和,然后再取平均值。

在第二部分中,您需要从数组中删除重复项。我使用了额外的索引数组来复制每个元素不超过一次。如果您对 xygreyscale 有一些禁止值,则可以使用它,因此无需额外的数组即可相处。

struct PLY {
    int x;
    int y;
    int greyscale;
};

int main()
{
    struct comp
    {
        bool operator()(const PLY &a, const PLY &b) { return a.x != b.x ? a.x < b.x : a.y < b.y; }
    };
    vector<PLY> v{ {1,1,1}, {1,2,2}, {1,1,2}, {1,3,5}, {1,2,7} };
    sort(begin(v), end(v), comp());

    vector<bool> ind(v.size(), true);
    int s = 0;
    for (int i = 1; i < v.size(); ++i)
    {
        if (v[i].x == v[i - 1].x &&v[i].y == v[i - 1].y)
        {
            v[s].greyscale += v[i].greyscale;
            ind[i] = false;
        }
        else
        {
            int d = i - s;
            if (d != 1)
            {
                v[s].greyscale /= d;
            }
            s = i;
        }
    }

    s = 0;
    for (int i = 0; i < v.size(); ++i)
    {
        if (ind[i])
        {
            if (s != i)
            {
                v[s] = v[i];
            }
            ++s;
        }
    }
    v.resize(s);
}

【讨论】:

    【解决方案3】:

    所以你需要检查一下,PLY a1 { 1,1,1 }; 是否与 PLY a2 {2,2,1}; 重复
    如此简单的方法是覆盖operator == 以检查a1.x == a2.xa1.y == a2.y。在您编写自己的函数removeDuplicates(std::vector&lt;PLU&gt;&amp; mPLY); 后,将使用该向量的迭代器,进行比较并删除。但如果您想过于频繁地从数组中间删除,最好使用std::list

    【讨论】:

    • 不同意最后一句话:在这种情况下,删除也可以在std::vector 上有效(以线性时间)完成(例如,参考std::remove_if 的实现)
    • 我编辑了我的问题并添加了一个重复的示例。
    • 是的,您编辑的问题完全符合我对迭代器比较的回答。要从列表中间删除,您只需要切换指针。要从向量中删除,您需要移动完整向量。复杂性std::remove_if 恰好是谓词的std::distance(first, last) 应用程序。
    • @Warezovvv 您的意思是在 O(N^2) 时间内比较所有元素对?还可以考虑对vector 进行排序并使用两个指针或使用std::unordered_map 以更快地完成此操作。
    猜你喜欢
    • 1970-01-01
    • 2021-08-26
    • 2013-08-11
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    相关资源
    最近更新 更多