web-dev-qa-db-ja.com

コンテナ内の1つのアイテムだけが述語を満たすかどうかを判断できるSTLアルゴリズムは何ですか?

述語とコレクションを受け取り、コレクションの1つだけのメンバーが述語を満たす場合はtrueを返し、そうでない場合はfalseを返すSTLアルゴリズムが必要です。

STLアルゴリズムを使用してこれをどのように実行しますか?

たとえば、以下をSTLアルゴリズムコードに置き換えて、同じ戻り値を表現します。

int count = 0;

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

return 1 == count;
46
WilliamKF

2つのことが頭に浮かびます。

std::count_if 次に、結果を1と比較します。

たとえば最初の2つの要素が既に述語と一致する場合に、コンテナー全体のトラバースを回避するには、一致する要素を探す2つの呼び出しを使用します。の線に沿った何か

auto it = std::find_if(begin,end,predicate);
if (it == end) return false;
++it;
return std::none_of(it,end,predicate);

または、よりコンパクトにしたい場合:

auto it = std::find_if(begin,end,predicate); 
return (it != end) && std::none_of(std::next(it),end,predicate);

クレジットは、コンパクト化にはRemy Lebeauに、ブラケット解除にはDeduplicatorに、そして none_of stdアルゴリズムも使用できることを認識するためのBlastfurnanceに送られます。

78

std::count_if を使用できます カウントし、それが1の場合は返します。

例えば:

#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;
}

Update:ただし、std::count_ifはコンテナー内の要素全体をカウントするため、質問で指定されたアルゴリズムとしては適していません。標準アルゴリズムのコレクションを使用した最良のアプローチは、@ formerlyknownas_463035818の回答に記載されています。

そうは言っても、OPのアプローチは、上記の最良の標準的なアプローチと同様に優れており、count2に到達すると短絡が発生します。誰かが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;
}

generalizedできるようになったので、もう1つのパラメータを指定することで、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;
}
14
JeJo

formerlyknownas_463035818's answer から始めて、これは一般化して、述語を満たす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);
    }
}
11
Mark H

std::not_fn 述語を否定する

この質問のアルゴリズムの中核として(エレガントな組み合わせ std::find_if および std::none_of in 受け入れられた答え )は、失敗時に短絡して、コンテナの単項述語をスキャンし、一致した場合は残りのコンテナのスキャンを続行します述語の否定、否定子std::not_fn C++ 17で導入され、あまり役に立たなかったstd::not1およびstd::not2コンストラクト。

std::not_fnは、受け入れられた回答と同じ述語ロジックを実装します(std::find_if条件付きの後にstd::none_of)ですが、セマンティクスが多少異なり、後のステップ(std::none_ofstd::all_of 最初のステップで使用された単項述語のnegationの上(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
}

静的サイズのコンテナーのパラメーターパックアプローチ

この回答はすでに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
}

これはまた、初期の障害(「複数検出」)時に短絡しますが、上記のアプローチよりもいくつかの単純なブール比較が含まれます。

ただし、このアプローチには欠点があり、特に@PeterCordesで指摘されているように、多くの要素を持つコンテナ入力用の最適化されたコードに注意してください以下のコメント。コメントの引用(コメントが長期にわたって持続することが保証されていないため):

サイズが静的であるからといって、テンプレートを使用してループを完全に展開することをお勧めしません。結果のasmでは、これはとにかく停止するために反復ごとに分岐を必要とするので、ループ分岐である可能性もあります。 CPUはループ(コードキャッシュ、ループバックバッファー)の実行に適しています。コンパイラーはヒューリスティックに基づいて静的サイズのループを完全に展開しますが、aが巨大な場合、おそらくはこれをロールバックしません。最初のone_of実装には、gccやclangなどの通常の最新のコンパイラ、あるいはMSVCを想定して、すでに両方の長所があります。

8
dfri