【问题标题】:A way to filter range by indices, to get min_element from filtered indices only?一种按索引过滤范围的方法,仅从过滤后的索引中获取 min_element?
【发布时间】:2015-09-07 16:17:56
【问题描述】:

在这个问题is-there-a-way-to-iterate-over-at-most-n-elements-using-range-based-for-loop 的 cmets 中,还有一个额外的问题 - 这是否可能在容器上具有“索引视图”,即过滤掉一些索引的子范围。

此外,我遇到了一个问题,即从过滤掉一些索引的范围中查找最小值。

即是否可以用 std 和/或 boost 算法、过滤器替换以下代码 - 使其更具可读性和可维护性:

template <typename Range, typename IndexPredicate>
auto findMin(const Range& range, IndexPredicate ipred) 
     -> boost::optional<typename Range::value_type>
{
    bool found = false;
    typename Range::value_type minValue{};
    for (std::size_t i = 0; i < range.size(); ++i)
    {
        if (not ipred(i))
            continue;
        if (not found)
        {
            minValue = range[i];
            found = true;
        }
        else if (minValue > range[i])
        {
            minValue = range[i];
        }
    }
    if (found)
    {
        return minValue;
    }
    else
    {
        return boost::none;
    }
}

只是这样使用:

#include <iostream>
#include <vector>

int main() {
    std::vector<float> ff = {1.2,-1.2,2.3,-2.3};
    auto onlyEvenIndex = [](auto i){ return (i&1) == 0;};

    auto minValue = findMin(ff, onlyEvenIndex);
    std::cout << *minValue << std::endl;
}

【问题讨论】:

    标签: c++ templates c++11 boost c++14


    【解决方案1】:

    使用最近的标准range-v3 proposal

    #include <range/v3/all.hpp>
    #include <iostream>
    #include <vector>
    
    int main() 
    {
        std::vector<float> rng = {1.2,-1.2,2.3,-2.3};
    
        auto even_indices = 
            ranges::view::iota(0ul, rng.size()) | 
            ranges::view::filter([](auto i) { return !(i & 1); })
        ;
        auto min_ind = *ranges::min_element(
            even_indices, [&rng](auto L, auto R) { 
            return rng[L] < rng[R]; 
        });
        std::cout << rng[min_ind];
    }
    

    Live Example。请注意,语法与 Boost.Range 大致相似,但经过全面修改以利用 C++14(通用 lambda、自动返回类型推导等)

    【讨论】:

    • 这是对 C++17 的提议吗?可以在gcc4.9中使用吗?
    • Gcc 4.9 在 C++11 模式下工作,clang 用于 c++14 模式(gcc 5.1 有一些错误会阻止 c++14 模式)。该提案在 c++17 或 TS 的轨道上,可能与 c++17 一起。见github.com/ericniebler/range-v3
    【解决方案2】:

    解决这个问题的方法是超越 C++ 中过滤范围的自然方式。我的意思是——我们需要过滤索引的范围,而不是值的范围。但是我们从哪里得到索引的范围呢?有办法获取索引范围 - boost::irange。所以 - 看到这个:

    #include <boost/range/irange.hpp>
    #include <boost/range/adaptor/filtered.hpp>
    #include <boost/range/algorithm/min_element.hpp>
    #include <functional>
    
    template <typename Range, typename IndexPredicate>
    auto findMin(const Range& range, IndexPredicate ipred) -> boost::optional<typename Range::value_type>
    {
        using boost::adaptors::filtered;
        using boost::irange;
    
        auto filtered_indexes = irange(0u, range.size()) | filtered(std::function<bool(std::size_t)>{ipred});
    

    使用提升范围的一个缺点是 they have problems to use raw lambdas - 这就是我需要使用 std::function 的原因。

    嵌套步骤与使用 boost::min_element 一样简单 - 唯一要记住的是您应该比较值。不仅仅是索引:

    auto lessByValue = [&range] (auto lhs, auto rhs) 
    { 
          return range[lhs] < range[rhs]; 
    };
    

    最后的步骤:

    auto foundMin = boost::min_element(filtered_indexes, lessByValue);
    if (foundMin != std::end(filtered_indexes))
        return range[*foundMin];
    else 
        return boost::none;
    

    【讨论】:

      【解决方案3】:

      this answer 开始到前面的问题。可选择执行该问题增强中描述的任务。

      增加indexT 以支持跨步模板参数size_t stride=1:将++t; 替换为std::advance(t,stride);

      ItB base() const { return b+**a(); } 添加到indexing_iterator(这是为了以后)。

      添加template&lt;size_t stride&gt; using index_stride_range=range&lt;indexT&lt;size_t, stride&gt;&gt;;这是一个带有编译时间跨度的索引范围。

      intersect,它适用于两个index_stride_ranges。输出是步幅为gcd(lhs_stride, rhs_stride)index_stride_range。弄清楚它从哪里开始是另一项工作,但只是高中数学。请注意,index_rangeindex_stride_rangestride=1 的类型,因此此升级也适用于 index_ranges。

      升级index_filter 以将index_stride_range 作为第一个参数。实现是一样的(除了依赖升级的intersect)。

      every_nth_index&lt;N&gt;(offset),这是一个index_stride_range,从offsetsize_t(-(1+(abs(offset))%stride) - (size_t(-(1+(abs(offset)%stride)))%stride) 或类似的(在简单的情况下基本上是0 到无穷大——额外的数学是找到最大的相等的数偏移+k*步幅,适合size_t

      现在我们得到:

      auto filtered = index_filter( every_nth_index<2>(), container );
      auto it = (std::min)( filtered.begin(), filtered.end() );
      

      我们有一个迭代器。 it.base() 会将迭代器返回到包含 it!=filtered.end(); 元素的 container 中(不是 it.base()!=container.end(),这是不同的)。

      【讨论】:

      • @PiotrNycz 上一个答案的一部分依赖于随机访问迭代器(因为它存储了start 迭代器和一个索引,并且在取消引用时使用了*(start+index))。但是是的,可以编写变体来支持“跳过每个第二个值”或其他任何内容。我只是注意到我在链接问题中提供的答案几乎也可以解决这个问题。
      猜你喜欢
      • 1970-01-01
      • 2016-06-11
      • 2013-02-12
      • 2018-06-03
      • 2020-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多