繁体   English   中英

如何有效地将唯一对象从一个矢量复制到另一个矢量(由相同对象的子集组成)?

[英]How do I efficiently copy unique objects from one vector to another (which is made up of a subset of identical objects)?

如何有效地将对象(或一系列对象)从向量A复制到向量B,

其中向量B已经包含与向量A相同的某些对象,

这样,从向量A复制的任何对象都不会在向量B中列出?

我有一个图形存储为std::vector<MinTreeEdge>minTreeInput的边缘std::vector<MinTreeEdge>minTreeInput

我有一个从此图创建的最小生成树,存储在std::vector<MinTreeEdge>minTreeOutput

我正在尝试向minTreeOutput随机添加一定数量的边。 为此,我想将元素从minTreeInput复制回minTreeOutput直到后者包含所需数量的边。 当然,复制的每个边缘对象都必须尚未存储minTreeOutput 此图中不能有重复的边。

以下是我到目前为止提出的内容。 它可以工作,但是它确实很长,我知道循环必须根据图形和树运行很多次。 我想知道如何正确执行此操作:

    // Edge class
    struct MinTreeEdge
    {
        // For std::unique() between objects
        bool operator==(MinTreeEdge const &rhs) const noexcept
        {
            return lhs == rhs.lhs;
        }
        int lhs;

        int node1ID;
        int node2ID;
        int weight;
        ......
    };

             ......

    // The usage
    int currentSize = minTreeOutput.size();
    int targetSize = currentSize + numberOfEdgesToReturn;
    int sizeDistance = targetSize - currentSize;
    while(sizeDistance != 0)
    {
        //Probably really inefficient

        for(std::vector<MinTreeEdge>::iterator it = minTreeInput.begin(); it != minTreeInput.begin()+sizeDistance; ++it)
            minTreeOutput.push_back(*it);

        std::vector<MinTreeEdge>::iterator mto_it;
        mto_it = std::unique (minTreeOutput.begin(), minTreeOutput.end());

        currentSize = minTreeOutput.size();
        sizeDistance = targetSize - currentSize;
    }

或者,是否有一种方法可以只列出minTreeInput (图形)中不在 minTreeOutput (树)中的所有边,而不必对照后者检查前者中的每个元素?

如何有效地将向量A中的对象(或一系列对象)复制到向量B中,其中向量B已经包含与向量A中的对象相同的某些对象,从而使得从向量A复制的对象中没有任何对象已列在向量B中?

如果我正确理解了这个问题,可以这样解释:“如何创建两个向量的集合并集 ?”。

答案:带有std::set_union

set_union,可廉价复制MinTreeEdge

请注意,要使其正常工作,需要对两个向量进行排序。 正如您已经提到的,这是出于效率原因。

#include <vector>
#include <algorithm>
#include <cassert>
#include <iterator>

struct MinTreeEdge
    {
        // For std::unique() between objects
        bool operator==(MinTreeEdge const &rhs) const noexcept
        {
            return lhs == rhs.lhs;
        }
        int lhs;

        int node1ID;
        int node2ID;
        int weight;
    };

struct lower_lhs
{
  bool operator()(const MinTreeEdge& l, const MinTreeEdge& r) const noexcept
  {
    return l.lhs < r.lhs;
  }
};

std::vector<MinTreeEdge> merge(std::vector<MinTreeEdge> a, 
                               std::vector<MinTreeEdge> b)
{
  // let's pessimistically assume that the inputs are not sorted
  // we could simply assert that they are if the caller is aware of
  // the requirement

  std::sort(a.begin(), a.end(), lower_lhs());
  std::sort(b.begin(), b.end(), lower_lhs());

  // alternatively...
  // assert(std::is_sorted(a.begin(), a.end(), lower_lhs()));
  // assert(std::is_sorted(b.begin(), b.end(), lower_lhs()));

  // optional step if the inputs are not already `unique`
  a.erase(std::unique(a.begin(), a.end()), a.end());
  b.erase(std::unique(b.begin(), b.end()), b.end());

  std::vector<MinTreeEdge> result;
  result.reserve(a.size() + b.size());

  std::set_union(a.begin(), a.end(),
                        b.begin(), b.end(),
                        std::back_inserter(result), 
                        lower_lhs());

  return result;
}

int main()
{
  // example use case

  auto a = std::vector<MinTreeEdge>{};
  auto b = std::vector<MinTreeEdge>{};

  b = merge(std::move(a), std::move(b));
}

set_union,MinTreeEdge的复制成本很高

为了达到这一目的,有人提到了集合。 可以公平地说,如果:

  1. MinTreeEdge 复制 MinTreeEdge昂贵
  2. 很多

那么我们可以期望在使用unordered_set看到性能上的好处。 但是,如果对象复制成本很高,那么我们可能希望通过引用将它们存储在我们的临时集中。

我可能会这样:

// utility class which converts unary and binary operations on
// a reference_wrapper into unary and binary operations on the 
// referred-to objects
template<class unary, class binary>
struct reference_as_object
{
    template<class U>
    decltype(auto) operator()(const std::reference_wrapper<U>& l) const {
        return _unary(l.get());
    }

    template<class U, class V>
    decltype(auto) operator()(const std::reference_wrapper<U>& l,
                              const std::reference_wrapper<V>& r) const {
        return _binary(l.get(), r.get());
    }

    unary _unary;
    binary _binary;
};

// utility to help prevent typos when defining a set of references
template<class K, class H, class C> using unordered_reference_set =
std::unordered_set<
std::reference_wrapper<K>,
reference_as_object<H, C>,
reference_as_object<H, C>
>;

// define unary and binary operations for our set. This way we can
// avoid polluting MinTreeEdge with artificial relational operators

struct mte_hash
{
    std::size_t operator()(const MinTreeEdge& mte) const
    {
        return std::hash<int>()(mte.lhs);
    }
};

struct mte_equal
{
    bool operator()(MinTreeEdge const& l, MinTreeEdge const& r) const
    {
        return l.lhs == r.lhs;
    }
};

// merge function. arguments by value since we will be moving
// *expensive to copy* objects out of them, and the vectors themselves
// can be *moved* into our function very cheaply

std::vector<MinTreeEdge> merge2(std::vector<MinTreeEdge> a,
                                std::vector<MinTreeEdge> b)
{
    using temp_map_type = unordered_reference_set<MinTreeEdge, mte_hash, mte_equal>;

    // build a set of references to existing objects in b
    temp_map_type tmap;
    tmap.reserve(b.capacity());

    // b first, since the requirements mentioned 'already in B'
    for (auto& ob : b) { tmap.insert(ob); }

    // now add missing references in a
    for (auto& oa : a) { tmap.insert(oa); }

    // now build the result, moving objects from a and b as required
    std::vector<MinTreeEdge> result;
    result.reserve(tmap.size());

    for (auto r : tmap) {
        result.push_back(std::move(r.get()));
    }

    return result;

    // a and b now have elements which are valid but in an undefined state
    // The elements which are defined are the duplicates we don't need
    // on summary, they are of no use to us so we drop them.
}

修剪-MinTreeEdge复制起来很昂贵,但移动起来非常便宜

假设我们想坚持使用vector方法(几乎总是应该这样做),但是MinTreeEdge复制起来有点贵。 说它为内部多态性使用了pimpl习惯用法,这将不可避免地意味着在副本上进行内存分配。 但是,我们可以说它的价格便宜。 我们还要想象一下,在将数据发送给我们之前,不会期望调用者对数据进行排序或唯一化。

使用标准算法和向量,我们仍然可以达到良好的效率:

std::vector<MinTreeEdge> merge(std::vector<MinTreeEdge> a,
                               std::vector<MinTreeEdge> b)
{
    // sorts a range if not already sorted
    // @return a reference to the range
    auto maybe_sort = [] (auto& c) -> decltype(auto)
    {
        auto begin = std::begin(c);
        auto end = std::end(c);
        if (not std::is_sorted(begin, end, lower_lhs()))
            std::sort(begin, end, lower_lhs());
        return c;
    };

    // uniqueify a range, returning the new 'end' of
    // valid data
    // @pre c is sorted
    // @return result of std::unique(...)
    auto unique = [](auto& c) -> decltype(auto)
    {
        auto begin = std::begin(c);
        auto end = std::end(c);
        return std::unique(begin, end);
    };

    // turn an iterator into a move-iterator        
    auto mm = [](auto iter) { return std::make_move_iterator(iter); };


    std::vector<MinTreeEdge> result;
    result.reserve(a.size() + b.size());

    // create a set_union from two input containers.
    // @post a and b shall be in a valid but undefined state

    std::set_union(mm(a.begin()), mm(unique(maybe_sort(a))),
                   mm(b.begin()), mm(unique(maybe_sort(b))),
                   std::back_inserter(result),
                   lower_lhs());

    return result;
}

如果提供了一个免费函数void swap(MinTreeEdge& l, MinTreeEdge& r) nothrow则此函数将需要精确的N次移动,其中N是结果集的大小。 由于在pimpl类中,移动只是指针交换,因此该算法仍然有效。

由于您的输出向量不应包含重复项,因此一种完成不存储重复项的方法是将输出容器更改为std::set<MinEdgeTree>而不是std::vector<MinEdgeTree> 原因是std::set不存储重复项,因此您不必自己编写代码即可执行此检查。

首先,您需要为MinEdgeTree类定义一个operator <

 struct MinTreeEdge
 {
     // For std::unique() between objects
     bool operator==(MinTreeEdge const &rhs) const noexcept
     {
         return lhs == rhs.lhs;
     }
     // For std::unique() between objects
     bool operator<(MinTreeEdge const &rhs) const noexcept
     {
         return lhs < rhs.lhs;
     }
//...
};

完成后,可以用以下内容替换while循环:

#include <set>
#include <vector>
#include <iterator>
#include <algorithm>
//...
std::vector<MinTreeEdge> minTreeInput;
//...
std::set<MinTreeEdge> minTreeOutput;
//...
std::copy(minTreeInput.begin(), minTreeInput.end(), 
          std::inserter(minTreeOutput, minTreeOutput.begin())); 

根本不需要调用std::unique ,因为它将是std::set来检查重复项。

如果输出容器必须保持为std::vector ,则仍可以使用临时std::set来执行上述操作,然后将std::set复制到输出矢量:

std::vector<MinTreeEdge> minTreeInput;
std::vector<MinTreeEdge> minTreeOutput;
//... 
std::set<MinTreeEdge> tempSet;
std::copy(minTreeInput.begin(), minTreeInput.end(), 
          std::inserter(tempSet, tempSet.begin())); 

std::copy(tempSet.begin(), tempSet.end(),std::back_inserter(minTreeOutput));

您可以使用以下内容:

struct MinTreeEdge
{
    bool operator<(MinTreeEdge const &rhs) const noexcept
    {
        return id < rhs.id;
    }
    int id;

    int node1ID;
    int node2ID;
    int weight;
};

std::vector<MinTreeEdge> CreateRandomGraph(const std::vector<MinTreeEdge>& minSpanningTree,
                                           const std::vector<MinTreeEdge>& wholeTree,
                                           std::mt19937& rndEng,
                                           std::size_t expectedSize)
{
    assert(std::is_sorted(minSpanningTree.begin(), minSpanningTree.end())); 
    assert(std::is_sorted(wholeTree.begin(), wholeTree.end())); 
    assert(minSpanningTree.size() <= expectedSize);
    assert(expectedSize <= wholeTree.size());

    std::vector<MinTreeEdge> res;
    std::set_difference(wholeTree.begin(), wholeTree.end(),
                        minSpanningTree.begin(), minSpanningTree.end(),
                        std::back_inserter(res));

    std::shuffle(res.begin(), res.end(), rndEng);
    res.resize(expectedSize - minSpanningTree.size());
    res.insert(res.end(), minSpanningTree.begin(), minSpanningTree.end());
    // std::sort(res.begin(), res.end());
    return res;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM