如何找到已排序容器的匹配元素的索引?

Posted

技术标签:

【中文标题】如何找到已排序容器的匹配元素的索引?【英文标题】:How to find the indices of matching elements of sorted containers? 【发布时间】:2019-07-03 17:38:23 【问题描述】:

我正在尝试获取元素匹配的一个容器的索引。两个容器都按升序排序。是否有一种算法或算法组合可以将已排序容器的匹配元素的索引放入另一个容器中?

我已经编写了一个算法,但想知道之前是否以某种我没有想到的方式在 stl 中对其进行了编码?

我希望算法的运行复杂度与我建议的算法相当,我相信它是 O(min(m, n))。

#include <iterator>
#include <iostream>

template <typename It, typename Index_it>
void get_indices(It selected_it, It selected_it_end, It subitems_it, It subitems_it_end, Index_it indices_it)

    auto reference_it = selected_it;
    while (selected_it != selected_it_end && subitems_it != subitems_it_end) 
        if (*selected_it == *subitems_it) 
            *indices_it++ = std::distance(reference_it, selected_it);
            ++selected_it;
            ++subitems_it;
        
        else if (*selected_it < *subitems_it) 
            ++selected_it;
        
        else 
            ++subitems_it;
        
    


int main()

    int items[] =  1, 3, 6, 8, 13, 17 ;
    int subitems[] =  3, 6, 17 ;
    int indices[std::size(subitems)] = 0;
    auto selected_it = std::begin(items), it = std::begin(subitems);
    auto indices_it = std::begin(indices);
    get_indices(std::begin(items), std::end(items)
        , std::begin(subitems), std::end(subitems)
        , std::begin(indices));
    for (auto i : indices) 
        std::cout << i << ", ";
    
    return 0;

【问题讨论】:

你有没有找到任何你想要的东西here?如果没有,那么可能没有这样的事情。 @πάνταῥεῖ,我做到了,但有时我看不到某些算法组合可能会产生我正在寻找的东西。 很难结合标准库中的算法来做这些事情,因为这些算法主要关心迭代器,而不是索引......你想要的基本上是set_intersection,索引在第一个范围内而不是价值,但你不会找到这样的东西。 @Holt,如果它生成了一个我可以从中生成索引的迭代器列表,它甚至会很有用,但它看起来也不是那样。 感觉如果我可以将转换附加到 back_inserter,我可以生成我需要的东西。 【参考方案1】:

我们可以使用find_if来简化函数的实现:

template<class SourceIt, class SelectIt, class IndexIt>
void get_indicies(SourceIt begin, SourceIt end, SelectIt sbegin, SelectIt send, IndexIt dest) 
    auto scan = begin; 

    for(; sbegin != send; ++sbegin) 
        auto&& key = *sbegin; 
        scan = std::find_if(scan, end, [&](auto&& obj)  return obj >= key; ); 
        if(scan == end) break;
        for(; scan != end && *scan == key; ++scan) 
            *dest = std::distance(begin, scan); 
            ++dest; 
        
    

这并没有让它变得更短,但是现在代码看起来更简洁了。您正在扫描,直到找到与密钥一样大或等于密钥的东西,然后只要源与密钥匹配,就将索引复制到目的地。

【讨论】:

【参考方案2】:

也许我误解了这个问题。但是算法库里有一个函数。

std::set_intersection

这就是你想要的一个功能。见:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main()

    // Input values
    std::vector<int> items 1,3,6,8,13,17 ;
    std::vector<int> subitems 3,6,17 ;

    // Result
    std::vector<int> result;

    // Do the work. One liner
    std::set_intersection(items.begin(),items.end(), subitems.begin(),subitems.end(),std::back_inserter(result));

    // Debug output: Show result
    std::copy(result.begin(), result.end(), std::ostream_iterator<int>(std::cout, " "));
    return 0;


如果我误解了,请告诉我,我会找到另一个解决方案。

编辑:

我确实误会了。你想要索引。那么可能是这样的?

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using Iter = std::vector<int>::iterator;

int main()

    // Input values
    std::vector<int> items 1,3,6,8,13,17 ;
    std::vector<int> subitems 3,6,17 ;

    // Result
    std::vector<int> indices;
    Iter it;

    // Do the work.
    std::for_each(subitems.begin(), subitems.end(), [&](int i) it = find(items.begin(), items.end(), i); if (it != items.end()) indices.push_back(std::distance(items.begin(),it)););

    // Debug output: Show result
    std::copy(indices.begin(), indices.end(), std::ostream_iterator<int>(std::cout, " "));
    return 0;

不幸的是,这是一个很长的“单行”。

我需要多想。 . .

【讨论】:

是的,@Holt 提到了这一点,但它没有给出元素的索引或每个元素的迭代器。也许如果 std::back_inserter 能够返回元素的指针,我可以使用它。【参考方案3】:

答案是肯定的,但它会附带C++20

您可以为此使用ranges:

首先用一些你喜欢的谓词创建一个view

auto result = items | ranges::view::filter(predicate);

然后将iteratorbase 带到原始数组,例如result.begin().base() 将为您提供指向原始数组中resultbegin 的迭代器。

#include <algorithm>
#include <iostream>
#include <vector>
#include <iterator>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp>

int main()


   std::vector<int> items =  1, 3, 6, 8, 13, 17 ;
   std::vector<int> subitems =  3, 6, 17 ;
   auto predicate = [&](int& n)
       for(auto& s : subitems)
        if(n == s)
            return true;
        return false; 
        ;
   auto result = items | ranges::view::filter(predicate);

   for (auto& n : result)
   
      std::cout << n << '\n';
   
   for(auto it = result.begin(); it != result.end(); ++it )
    std::cout << it.base() - items.begin() << ' ';


见godbolt

【讨论】:

您的predicate 使用循环,最好用std::find 替换。但无论如何,这会导致 O(n*m) 算法,我试图避免这种情况。 @Adrian the big O 不是问题的一部分。不过,更好的谓词可以完成这项工作。【参考方案4】:

通过使用std::set_intersection,定义assignment_iterator 类和assignment 助手,这是可能的:

#include <iterator>
#include <iostream>
#include <algorithm>
#include <vector>

template <typename Transform>
class assignment_iterator

    Transform transform;

public:
    using iterator_category = std::output_iterator_tag;
    using value_type        = void;
    using difference_type   = void;
    using pointer           = void;
    using reference         = void;

    assignment_iterator(Transform transform)
        : transform(transform)
    

    // For some reason VC++ is assigning the iterator inside of std::copy().
    // Not needed for other compilers.
    #ifdef _MSC_VER
    assignment_iterator& operator=(assignment_iterator const& copy)
    
        transform.~Transform();
        new (&transform) Transform(copy.transform);
        return *this;
    
    #endif

    template <typename T>
    constexpr assignment_iterator& operator=(T& value) 
        transform(value);
        return *this;
    

    constexpr assignment_iterator& operator* (   )  return *this; 
    constexpr assignment_iterator& operator++(   )  return *this; 
    constexpr assignment_iterator& operator++(int)  return *this; 
;

template <typename Transform>
assignment_iterator<Transform> assignment(Transform&& transform)

    return  std::forward<Transform>(transform) ;


int main()

    int items[] =  1, 3, 6, 8, 13, 17 ;
    int subitems[] =  3, 6, 17 ;
    std::vector<int> indices;
    std::set_intersection(std::begin(items), std::end(items)
        , std::begin(subitems), std::end(subitems)
        , assignment([&items, &indices](int& item) 
            return indices.push_back(&item - &*std::begin(items));
        )
    );

    std::copy(indices.begin(), indices.end()
        , assignment([&indices](int& index) 
            std::cout << index;
            if (&index != &std::end(indices)[-1])
              std::cout <<  ", ";
        )
    );
    return 0;

Demo

这是更多的代码,但也许 assignment 是一种更通用的方法来执行其他操作,目前需要像 back_inserterostream_iterator 这样的特定实现,因此从长远来看代码更少(例如,像上面还有std::copy)的其他用途?

根据文档here,这应该始终正常工作:

元素将从第一个范围复制到目标范围。

【讨论】:

【参考方案5】:

你可以使用 std::find 和 std::distance 找到匹配的索引,然后放入容器中。

#include <vector>
#include <algorithm>

int main ()

   std::vector<int> v = 1,2,3,4,5,6,7;
   std::vector<int> matchIndexes;
   std::vector<int>::iterator match = std::find(v.begin(), v.end(), 5);
   int index = std::distance(v.begin(), match);
   matchIndexes.push_back(index);

   return 0;

要匹配多个元素,您可以以类似的方式使用 std::search。

【讨论】:

但这与元素列表不匹配。那只匹配一个。 虽然你可以,但你会有一个 O(n*m) 算法。我正在寻找比这更好的。我提供的是 O(min(n, m))。

以上是关于如何找到已排序容器的匹配元素的索引?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript DOM:在容器中查找元素索引

如何使用二分搜索将元素插入已排序的向量中

排序算法之选择排序

在已移动的排序数组中找到一个元素[重复]

如何在最后一个索引Swift中添加元素

如何在 int 数组中查找元素的索引?