多向量上的函数

Posted

技术标签:

【中文标题】多向量上的函数【英文标题】:Function on multiple vector 【发布时间】:2017-09-04 07:17:02 【问题描述】:

我有一个向量的排序算法,我想将它应用于多个向量,但不知道多少。我唯一确定的是,我将在其上执行我的算法的至少 1 个向量(始终相同)。其他人将跟随。

这是一个例子:

void sort(std::vector<int>& sortVector, std::vector<double>& follow1, std::vector<char>& follow2, ... )
    for (int i = 1; i<vector.size(); ++i)
        if ( vector[i-1] > vector[i] )  //I know it's not sorting here, it's only for the example
            std::swap(vector[i-1], vector[i]);
            std::swap(follow1[i-1], follow1[i]);
            std::swap(follow2[i-1], follow2[i]);
            ....
        
    
 

我正在考虑使用可变参数函数,但由于它是一个递归函数,我想知道每次创建我的 va_arg 列表是否不会花费太多时间(我正在处理大小为 500millions/1billions 的向量... )。那么还有别的东西存在吗?

在我写这个问题的时候,我明白也许我在自欺欺人,没有其他方法可以实现我想要的,可变参数函数可能不会那么长。 (其实我真的不知道)。

编辑: 事实上,我正在对数据进行八叉树排序,以便在 opengl 中可用。 由于我的数据并不总是相同的(例如 OBJ 文件会给我法线,PTS 文件会给我强度和颜色,...),我希望能够重新排序我的所有向量(其中包含我的数据)所以它们与位置向量具有相同的顺序(包含我的点位置的向量,它会一直在这里)。

但是我所有的向量都将具有相同的长度,我希望我的所有跟随向量都重新组织为第一个。

如果我有 3 个向量,如果我交换第一个向量中的第一个和第三个值,我想交换其他 2 个向量中的第一个和第三个值。

但我的向量并不完全相同。有些是std::vector&lt;char&gt;,其他是std::vector&lt;Vec3&gt;std::vector&lt;unsigned&gt;,等等。

【问题讨论】:

你确定你实际上没有向量的向量吗?你打算如何将这些参数传递给函数,而不知道你有多少? 不要编写自己的sort 函数。想想如何重构数据以使用 std::sort。 其实点云是八叉树排序的。而且我的数据可能来自任何地方,这意味着每次我都会有位置,但我可以有颜色、强度、法线……或者没有。所以对于我拥有的每一个信息,我都必须将我的排序应用于这个向量。我不确定我是否可以将 std::sort 用于这样的事情:/ 是的,有很多方法可以对您的数据使用 std::sort。最简单的方法是对整数数组(0..N-1)进行排序,其中比较函数使用您的第一个数组。结果将是索引的排列,您可以使用它来重新排列任意数量的数组。 不清楚你到底想做什么。我提供了一个我认为不符合您要求的答案。请添加一些说明。如果我了解您的需求,我相信我可以提供答案。所有向量的长度都相同吗?您想对第一个向量进行排序,然后将所有其他向量重新排序为第一个?例如。如果第一个向量中的第 3 个元素在排序后移到第 1 个位置,那么在所有其他向量中,第 3 个元素要移动到第 1 个位置? 【参考方案1】:

对于range-v3,您可以使用zip,类似:

template <typename T, typename ... Ranges>
void sort(std::vector<T>& refVector, Ranges&& ... ranges)
    ranges::sort(ranges::view::zip(refVector, std::forward<Ranges>(ranges)...));

Demo

或者,如果您不想使用范围进行比较(对于 refVector 中的平局),您可以投影以仅使用 refVector

template <typename T, typename ... Ranges>
void sort(std::vector<T>& refVector, Ranges&& ... ranges)
    ranges::sort(ranges::view::zip(refVector, std::forward<Ranges>(ranges)...),
                 std::less<>,
                 [](auto& tup) -> T&  return std::get<0>(tup); );

【讨论】:

按哪些标准进行排序? 我让字典顺序(所以refVector,然后ranges 一个)。但我们只能在refVector 上轻松使用投影。【参考方案2】:

虽然,我完全同意 n.m. 的评论。我建议使用包含跟随向量的向量向量,而不是对所有跟随向量进行循环。

void sort(std::vector<int>& vector, std::vector<std::vector<double>>& followers)
    for (int i = 1; i<vector.size(); ++i)
        if ( vector[i-1] > vector[i] )  
            std::swap(vector[i-1], vector[i]);
            for (auto & follow : followers) 
                std::swap(follow[i-1], follow[i]);          
        
    
 

尽管如此,作为 n.m.指出,也许考虑将您喜欢排序的所有数据放入类结构中。比你可以拥有你的类的向量并应用 std::sort,see here。

struct MyStruct

    int key;  //content of your int vector named "vector"
    double follow1; 
    std::string follow2;
    // all your inforrmation of the follow vectors go here.

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) 
;

struct less_than_key

    inline bool operator() (const MyStruct& struct1, const MyStruct& struct2)
    
        return (struct1.key < struct2.key);
    
;

std::vector < MyStruct > vec;

vec.push_back(MyStruct(4, 1.2, "test"));
vec.push_back(MyStruct(3, 2.8, "a"));
vec.push_back(MyStruct(2, 0.0, "is"));
vec.push_back(MyStruct(1, -10.5, "this"));

std::sort(vec.begin(), vec.end(), less_than_key());

【讨论】:

这真的是一个可行的选择吗?每次在向量向量中搜索以交换 2 个值? 如果我的理解正确,您就不必在其中搜索了。我编辑我的答案。 事实上,我可以有 char, vec3, double, int, ... 所以 std::vector<:vector>> 的向量是行不通的 :/ 是在我的帖子中不清楚,将纠正这个...... 我没看到。【参考方案3】:

这里的主要问题是std::sort算法不能同时对多个向量进行操作。

出于演示的目的,假设您有一个std::vector&lt;int&gt; v1 和一个std::vector&lt;char&gt; v2(当然大小相同),并且您希望根据v1 中的值对两者进行排序。为了解决这个问题,我基本上看到了三种可能的解决方案,所有这些都可以推广到任意数量的向量:


1) 将所有数据放入一个向量中。

定义一个struct,比如Data,它保留每个数据向量的条目。

struct Data 

    int d1;
    char d2;
    // extend here for more vectors
;

现在构造一个新的std::vector&lt;Data&gt; 并用你的原始向量填充它:

std::vector<Data> d(v1.size());
for(std::size_t i = 0; i < d.size(); ++i)

    d[i].d1 = v1[i];
    d[i].d2 = v2[i];
    // extend here for more vectors

由于现在所有内容都存储在单个向量中,因此您可以使用std::sort 将其整理好。由于我们希望根据存储第一个向量的值的第一个条目 (d1) 对其进行排序,因此我们使用自定义谓词:

std::sort(d.begin(), d.end(), 
    [](const Data& l, const Data& r)  return l.d1 < r.d1; );

之后,所有数据都根据第一个向量的值在d 中排序。您现在可以使用组合向量 d 或将数据拆分为原始向量:

std::transform(d.begin(), d.end(), v1.begin(), 
    [](const Data& e)  return e.d1; );
std::transform(d.begin(), d.end(), v2.begin(),
    [](const Data& e)  return e.d2; );
// extend here for more vectors

2) 使用第一个vector 计算排序范围的索引,并使用这些索引将所有vectors 排序:

首先,您将第一个vector 中的所有元素附加到它们的当前位置。然后使用std::sort 和一个只比较值(忽略位置)的谓词对其进行排序。

template<typename T>
std::vector<std::size_t> computeSortIndices(const std::vector<T>& v)

    std::vector<std::pair<T, std::size_t>> d(v.size());
    for(std::size_t i = 0; i < v.size(); ++i)
        d[i] = std::make_pair(v[i], i);

    std::sort(d.begin(), d.end(),
        [](const std::pair<T, std::size_t>& l, 
            const std::pair<T, std::size_t>& r)
        
            return l.first < r.first;
        );

    std::vector<std::size_t> indices(v.size());
    std::transform(d.begin(), d.end(), indices.begin(),
        [](const std::pair<T, std::size_t>& p)  return p.second; );
    return indices;

在结果索引vector 中,0 位置的条目是8,那么这告诉您vector 条目必须在已排序的vectors 中排在第一位在原始范围内的位置8

然后您使用此信息对您的所有vectors 进行排序:

template<typename T>
void sortByIndices(std::vector<T>& v, 
    const std::vector<std::size_t>& indices)

    assert(v.size() == indices.size());
    std::vector<T> result(v.size());   
    for(std::size_t i = 0; i < indices.size(); ++i)
        result[i] = v[indices[i]];
    v = std::move(result);

任何数量的vectors 都可以这样排序:

const auto indices = computeSortIndices(v1);
sortByIndices(v1, indices);
sortByIndices(v2, indices);
// extend here for more vectors

这可以通过直接从computeSortIndices中提取排序后的v1来改进一点,这样就不需要再使用sortByIndices进行排序了。


3) 实现您自己的排序函数,该函数能够对多个vectors 进行操作。我已经勾画了一个就地合并排序的实现,它能够根据第一个中的值对任意数量的vectors 进行排序。

归并排序算法的核心是由multiMergeSortRec 函数实现的,它接受任意数量(> 0)的任意类型的向量。 该函数将所有向量分成前半部分和后半部分,对这两部分进行递归排序并将结果合并在一起。如果您需要更多详细信息,请在网上搜索有关合并排序的完整说明。

template<typename T, typename... Ts>
void multiMergeSortRec(
    std::size_t b, std::size_t e,
    std::vector<T>& v, std::vector<Ts>&... vs)

    const std::size_t dist = e - b;    
    if(dist <= 1)
        return;

    std::size_t m = b + (dist / static_cast<std::size_t>(2));
    // split in half and recursively sort both parts
    multiMergeSortRec(b, m, v, vs...);
    multiMergeSortRec(m, e, v, vs...);
    // merge both sorted parts    
    while(b < m)
    
        if(v[b] <= v[m])
            ++b;
        else 
        
            ++m;
            rotateAll(b, m, v, vs...);
            if(m == e)
                break;
        
    


template<typename T, typename... Ts>
void multiMergeSort(std::vector<T>& v, std::vector<Ts>&... vs)

    // TODO: check that all vectors have same length
    if(v.size() < 2)
        return ;
    multiMergeSortRec<T, Ts...>(0, v.size(), v, vs...);

为了就地操作,vectors 的某些部分必须旋转。这是由rotateAll 函数完成的,该函数通过递归处理可变参数包再次对任意数量的vectors 起作用。

void rotateAll(std::size_t, std::size_t)



template<typename T, typename... Ts>
void rotateAll(std::size_t b, std::size_t e, 
    std::vector<T>& v, std::vector<Ts>&... vs)

    std::rotate(v.begin() + b, v.begin() + e - 1, v.begin() + e);
    rotateAll(b, e, vs...);

请注意,rotateAll 的递归调用很可能被每个优化编译器内联,因此该函数仅将std::rotate 应用于所有向量。如果您留在原地并合并到一个额外的vector,您可以避免旋转部分矢量的需要。我想强调的是,这既不是优化的也不是经过全面测试的合并排序实现。它应该用作草图,因为您真的不想在处理大型向量时使用冒泡排序。


让我们快速比较一下上述替代方案:

1) 更容易实现,因为它依赖于现有的(高度优化和测试的)std::sort 实现。 1) 需要将所有数据复制到新的 vector 中,并且可能(取决于您的用例)将所有数据复制回来。 在 1) 如果您需要附加额外的vectors 进行排序,则必须扩展多个位置。 2) 的实现工作一般(多于 1,但比 3 少且容易),但它依赖于经过优化和测试的 std::sort。 2) 无法就地排序(使用索引),因此必须复制每个vector。也许有一个就地替代方案,但我现在想不出一个(至少是一个简单的)。 2) 很容易扩展为额外的vectors。 对于 3),您需要自己实现排序,这使得正确处理变得更加困难。 3) 不需要复制所有数据。该实现可以进一步优化,并且可以进行调整以提高性能(就地)或减少内存消耗(就地)。 3) 无需任何更改即可处理额外的vectors。只需使用一个或多个附加参数调用 multiMergeSort。 与std::vector&lt;std::vector&lt;&gt;&gt; 方法相比,这三种方法都适用于vectors 的异构集。

哪种替代方案在您的情况下表现更好,很难说,应该很大程度上取决于vectors 的数量及其大小,所以如果您真的需要最佳性能(和/或内存使用),您需要测量。

找到上述here的实现。

【讨论】:

【参考方案4】:

到目前为止,最简单的解决方案是创建一个用 std::iota(helper.begin(), helper.end(), size_t); 初始化的辅助向量 std::vector&lt;size_t&gt;

接下来,对这个数组进行排序。显然不是通过数组索引(iota 已经这样做了),而是通过sortvector[i]。 IOW,谓词是[sortvector&amp;](size_t i, size_t j) sortVector[i] &lt; sortVector[j];

您现在拥有正确的数组索引顺序。 IE。如果helper[0]==17,则表示所有向量的新前面应该是原来的第18个元素。通常产生排序结果的最简单方法是复制元素,然后交换原始向量和副本,对所有向量重复。但是,如果复制所有元素的成本太高,则可以就地完成。 (请注意,如果 O(N) 元素处理过于昂贵,那么简单的 std::sort 往往会表现不佳,并且需要枢轴)

【讨论】:

以上是关于多向量上的函数的主要内容,如果未能解决你的问题,请参考以下文章

菜菜的sklearn课堂笔记支持向量机-探索核函数在不同数据集上的表现

matlab中多目标线性规划函数如何使用

在函数中传递向量元素[关闭]

pyspark 数据框上的自定义函数

r语言match函数怎么用

支持向量机——核技巧