哪种STL算法可以确定容器中的一项是否恰好满足谓词?

Wil*_*mKF 46 c++ algorithm counting c++-standard-library c++11

我需要一个STL算法,该算法需要一个谓词和一个集合,并在集合中true只有一个成员满足该谓词的情况下返回,否则返回false

我将如何使用STL算法来做到这一点?

例如,用STL算法代码替换以下内容以表示相同的返回值。

int count = 0;

for( auto itr = c.begin(); itr != c.end(); ++itr ) {
    if ( predicate( *itr ) ) {
      if ( ++count > 1 ) {
        break;
      }
    }
}

return 1 == count;
Run Code Online (Sandbox Code Playgroud)

for*_*818 78

我想到两件事:

std::count_if然后将结果与进行比较1

为了避免在例如前两个元素已经匹配谓词的情况下遍历整个容器,我将使用两个调用来寻找匹配的元素。沿线的东西

auto it = std::find_if(begin,end,predicate);
if (it == end) return false;
++it;
return std::none_of(it,end,predicate);
Run Code Online (Sandbox Code Playgroud)

或者,如果您更喜欢它:

auto it = std::find_if(begin,end,predicate); 
return (it != end) && std::none_of(std::next(it),end,predicate);
Run Code Online (Sandbox Code Playgroud)

归功于Remy Lebeau进行了压缩,Deduplicator进行了分括号,Blastfurnance意识到我们也可以使用none_ofstd算法。

  • 我喜欢第二种选择的短路。 (13认同)
  • 有人可能会对此表示反对,但我只是不得不说。这样的答案使我想起了有时可以在标准算法库中找到的美丽。 (8认同)
  • @NathanOliver我一直在努力使自己对代码中的实际意图尽可能地明确,实际上并不是为了提高效率,而主要是为了使代码清晰。没有人要求对整个容器进行计数,那么为什么要编写代码呢。不幸的是,它并不总是像本例中那样容易 (2认同)

JeJ*_*eJo 14

您可以使用计数并返回是否为1。std::count_if

例如:

#include <iostream>
#include <algorithm> // std::count_if
#include <vector>    // std::vector
#include <ios>       // std::boolalpha

template<class Iterator, class UnaryPredicate>
constexpr bool is_count_one(Iterator begin, const Iterator end, UnaryPredicate pred)
{
    return std::count_if(begin, end, pred) == 1;
}

int main()
{
    std::vector<int> vec{ 2, 4, 3 };
    // true: if only one Odd element present in the container
    std::cout << std::boolalpha
              << is_count_one(vec.cbegin(), vec.cend(),
                  [](const int ele) constexpr noexcept -> bool { return ele & 1; });
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

更新:但是,std::count_if对容器中的整个元素进行计数,这不像问题中给出的算法那样好。@ formerlyknownas_463035818的答案中提到了使用标准算法集合的最佳方法。

话虽如此,OP的方法也像上述最佳标准方法一样好,当count达到时会发生短路2。如果有人对用于OP的方法的非标准算法模板功能感兴趣,就可以了。

#include <iostream>
#include <vector>    // std::vector
#include <ios>       // std::boolalpha
#include <iterator>  // std::iterator_traits

template<class Iterator, class UnaryPredicate>
bool is_count_one(Iterator begin, const Iterator end, UnaryPredicate pred)
{
    typename std::iterator_traits<Iterator>::difference_type count{ 0 };
    for (; begin != end; ++begin) {
        if (pred(*begin) && ++count > 1) return false;
    }
    return count == 1;
}

int main()
{
    std::vector<int> vec{ 2, 3, 4, 2 };
    // true: if only one Odd element present in the container
    std::cout << std::boolalpha
              << is_count_one(vec.cbegin(), vec.cend(),
                  [](const int ele) constexpr noexcept -> bool { return ele & 1; });
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在可以概括起来,通过提供另一个参数,N必须在容器中找到一个或多个元素的数量。

template<typename Iterator>
using diff_type = typename std::iterator_traits<Iterator>::difference_type;

template<class Iterator, class UnaryPredicate>
bool has_exactly_n(Iterator begin, const Iterator end, UnaryPredicate pred, diff_type<Iterator> N = 1)
{
    diff_type<Iterator> count{ 0 };
    for (; begin != end; ++begin) {
        if (pred(*begin) && ++count > N) return false;
    }
    return count == N;
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是错过了不超过2的关键要求。 (5认同)
  • 我的意思是一旦计数达到2,就不必继续计算谓词。 (3认同)

Mar*_*k H 10

从@ formerlyknownas_463035818的答案开始,可以将其概括为查看容器是否具有完全n满足谓词的项目。为什么?因为这是C ++,所以我们不满意,直到可以在编译时阅读电子邮件。

template<typename Iterator, typename Predicate>
bool has_exactly_n(Iterator begin, Iterator end, size_t count, Predicate predicate)
{
    if(count == 0)
    {
        return std::none_of(begin, end, predicate);
    }
    else
    {
        auto iter = std::find_if(begin, end, predicate);
        return (iter != end) && has_exactly_n(std::next(iter), end, count - 1, predicate);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我尝试了您的代码,但无法阅读我的电子邮件。请让我知道我在做什么错。谢谢。更严重的是,为什么不进一步将n设置为编译时值(模板非类型参数)呢? (3认同)
  • @CodyGray 我相信 `std::email_client` 概念已经被推回到 C++23。 (2认同)

dfr*_*fri 8

使用std::not_fn否定谓语

由于这一问题的核心算法(如已优雅结合覆盖std::find_if,并std::none_of接受的答案),与-短路短失败时,是扫描一个集装箱的一元谓词和满足时,继续扫描其余作为谓词否定的容器,我还要提到std::not_fnC ++ 17中引入的否定器,它代替了不太有用的std::not1std::not2构造。

我们可能会使用std::not_fn实现相同谓词逻辑为接受的答案(std::find_if有条件其次std::none_of),但与有些不同的语义,替换后一步骤(std::none_of与)std::all_of否定(在第一步骤中使用的一元谓词的std::find_if)。例如:

// C++17
#include <algorithm>   // std::find_if
#include <functional>  // std::not_fn
#include <ios>         // std::boolalpha
#include <iostream>
#include <iterator>  // std::next
#include <vector>

template <class InputIt, class UnaryPredicate>
constexpr bool one_of(InputIt first, InputIt last, UnaryPredicate p) {
  auto it = std::find_if(first, last, p);
  return (it != last) && std::all_of(std::next(it), last, std::not_fn(p));
}

int main() {
  const std::vector<int> v{1, 3, 5, 6, 7};
  std::cout << std::boolalpha << "Exactly one even number : "
            << one_of(v.begin(), v.end(), [](const int n) {
                 return n % 2 == 0;
               });  // Exactly one even number : true
}
Run Code Online (Sandbox Code Playgroud)

静态尺寸容器的参数包方法

由于我已经将答案限制在C ++ 14(及更高版本)中,因此,我将结合静态包容器和参数包扩展一起std::array使用一种静态大小容器的替代方法std::index_sequence

#include <array>
#include <ios>         // std::boolalpha
#include <iostream>
#include <utility>     // std::(make_)index_sequence

namespace detail {
template <typename Array, typename UnaryPredicate, std::size_t... I>
bool one_of_impl(const Array& arr, const UnaryPredicate& p,
                 std::index_sequence<I...>) {
  bool found = false;
  auto keep_searching = [&](const int n){
      const bool p_res = found != p(n);
      found = found || p_res;
      return !found || p_res;
  };
  return (keep_searching(arr[I]) && ...) && found;
}
}  // namespace detail

template <typename T, typename UnaryPredicate, std::size_t N,
          typename Indices = std::make_index_sequence<N>>
auto one_of(const std::array<T, N>& arr,
            const UnaryPredicate& p) {
  return detail::one_of_impl(arr, p, Indices{});
}

int main() {
  const std::array<int, 5> a{1, 3, 5, 6, 7};
  std::cout << std::boolalpha << "Exactly one even number : "
            << one_of(a, [](const int n) {
                 return n % 2 == 0;
               });  // Exactly one even number : true
}
Run Code Online (Sandbox Code Playgroud)

这也将在早期故障时短路(“发现多个”),但是将包含比上述方法更简单的布尔比较。

但是,请注意,这种方法可能有其缺点,特别是对于具有许多元素的容器输入的优化代码,如@PeterCordes在下面的评论中指出的那样。引用评论(因为不能保证评论会随着时间的推移而持续存在):

仅仅因为大小是静态的,并不意味着完全使用模板展开循环是个好主意。在生成的asm中,无论如何,每次迭代都需要一个分支来停止发现,因此也可能是循环分支。CPU擅长运行循环(代码缓存,回送缓冲区)。编译器将根据启发式方法完全展开静态大小的循环,但是如果庞大,则可能不会回滚a。因此one_of,假设使用普通的现代编译器(例如gcc或clang或MSVC),则您的第一个实现已兼顾了两者的优点

  • 仅仅因为大小是静态的,并不意味着完全使用模板展开循环是个好主意。在生成的asm中,无论如何,每次迭代都需要一个分支来停止发现,因此也可能是循环分支。CPU擅长运行循环(代码缓存,回送缓冲区)。编译器将根据试探法完全展开静态大小的循环,但是如果a很大,可能*不会*将其回滚。因此,假设使用普通的现代编译器(例如gcc或clang或MSVC),则您的第一个`one_of'实现已经兼有两全。 (4认同)
  • @PeterCordes感谢您的反馈,好点!由于我目前无法在工作中使用现代C ++功能,因此我担心我的一些答案可能会盲目偏向于使用我觉得很有趣的特定“新”(非传统...)核心语言功能。如果可以的话,我会在回答的结尾添加您的评论作为报价。由于评论可能不会随着时间的流逝而持续。 (2认同)