Development Tip

C ++ 표준 라이브러리에 transform_if가없는 이유는 무엇입니까?

yourdevel 2020. 10. 27. 23:35
반응형

C ++ 표준 라이브러리에 transform_if가없는 이유는 무엇입니까?


조건부 복사 (1.로 수행 가능)를 수행하고 copy_if싶지만 값 컨테이너에서 해당 값에 대한 포인터 컨테이너 (2. 로 수행 가능) 로의 사용 사례가 나타났습니다 transform.

사용 가능한 도구로 는 두 단계 미만으로 수행 할 수 없습니다 .

#include <vector>
#include <algorithm>

using namespace std;

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector
    vector<ha*> pv; // temporary vector
    // 1. 
    transform(v.begin(), v.end(), back_inserter(pv), 
        [](ha &arg) { return &arg; }); 
    // 2. 
    copy_if(pv.begin(), pv.end(), back_inserter(ph),
        [](ha *parg) { return parg->i < 2;  }); // 2. 

    return 0;
}

우리가 부를 수있는 당연히 remove_ifpv임시의 필요성을 제거, 더 나은 아직하지만, 그것은 할 어렵지 않다 구현 이 같은 (단항 작업) :

template <
    class InputIterator, class OutputIterator, 
    class UnaryOperator, class Pred
>
OutputIterator transform_if(InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperator op, Pred pred)
{
    while (first1 != last1) 
    {
        if (pred(*first1)) {
            *result = op(*first1);
            ++result;
        }
        ++first1;
    }
    return result;
}

// example call 
transform_if(v.begin(), v.end(), back_inserter(ph), 
[](ha &arg) { return &arg;      }, // 1. 
[](ha &arg) { return arg.i < 2; });// 2.
  1. 사용 가능한 C ++ 표준 라이브러리 도구에 대한보다 우아한 해결 방법이 있습니까?
  2. transform_if도서관에 존재하지 않는 이유 가 있습니까? 기존 도구의 조합이 충분한 해결 방법이고 / 또는 성능이 현명한 것으로 간주됩니까?

표준 라이브러리는 기본 알고리즘을 선호합니다.

컨테이너와 알고리즘은 가능하면 서로 독립적이어야합니다.

마찬가지로 기존 알고리즘으로 구성 할 수있는 알고리즘은 속기처럼 거의 포함되지 않습니다.

변환이 필요한 경우 간단하게 작성할 수 있습니다. / today /를 원하고 기성품을 작성하고 오버 헤드를 발생시키지 않으려면 Boost.Range 와 같은 게으른 범위 가있는 범위 라이브러리를 사용할 수 있습니다. 예 :

v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0)

@hvd가 주석에서 지적했듯이 transform_if이중 결과는 다른 유형 ( double이 경우)이됩니다. 구성 순서가 중요하며 Boost Range를 사용하면 다음과 같이 작성할 수도 있습니다.

 v | transformed(arg1 * arg1 / 7.0) | filtered(arg1 < 2.0)

결과적으로 다른 의미가 생깁니다. 이것은 요점을 집으로 가져옵니다.

그것은 거의 의미가 포함 std::filter_and_transform, std::transform_and_filter, std::filter_transform_and_filter표준 라이브러리로 등 등 .

Live On Coliru 샘플보기

#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

using namespace boost::adaptors;

// only for succinct predicates without lambdas
#include <boost/phoenix.hpp>
using namespace boost::phoenix::arg_names;

// for demo
#include <iostream>

int main()
{
    std::vector<int> const v { 1,2,3,4,5 };

    boost::copy(
            v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0),
            std::ostream_iterator<double>(std::cout, "\n"));
}

새로운 for 루프 표기법은 여러면에서 컬렉션의 모든 요소에 액세스하는 알고리즘의 필요성을 줄여줍니다. 이제 루프를 작성하고 논리를 제자리에 두는 것이 더 깔끔해졌습니다.

std::vector< decltype( op( begin(coll) ) > output;
for( auto const& elem : coll )
{
   if( pred( elem ) )
   {
        output.push_back( op( elem ) );
   }
}

이제 알고리즘에 넣을 수있는 많은 가치를 제공합니까? 예, 알고리즘은 C ++ 03에 유용했을 것이고 실제로 저는 그것을 위해 하나를 가지고 있었지만 지금은 필요하지 않으므로 추가 할 때 실질적인 이점이 없습니다.

실제 사용에서 코드가 항상 정확히 그런 것은 아닙니다. "op"및 "pred"함수가 반드시 있어야하는 것은 아니며 알고리즘에 "적합"되도록하기 위해 람다를 만들어야 할 수도 있습니다. 논리가 복잡한 경우 우려 사항을 분리하는 것이 좋지만 입력 유형에서 멤버를 추출하여 값을 확인하거나 컬렉션에 추가하는 문제라면 알고리즘을 사용하는 것보다 훨씬 간단합니다.

또한 어떤 종류의 transform_if를 추가하면 변환 전후에 술어를 적용할지 아니면 2 개의 술어를 두 위치에 모두 적용할지 여부를 결정해야합니다.

그래서 우리는 무엇을할까요? 3 개의 알고리즘을 추가 하시겠습니까? (컴파일러가 변환의 양쪽 끝에 술어를 적용 할 수있는 경우 사용자는 실수로 잘못된 알고리즘을 쉽게 선택할 수 있으며 코드는 여전히 컴파일되지만 잘못된 결과를 생성합니다).

또한 컬렉션이 큰 경우 사용자가 반복기로 반복하거나 매핑 / 축소를 원합니까? map / reduce를 도입하면 방정식이 훨씬 더 복잡해집니다.

기본적으로 라이브러리는 도구를 제공하며 사용자는 알고리즘의 경우와는 달리 다른 방식으로 수행하지 않고 원하는 작업에 맞게 도구를 사용합니다. (위의 사용자가 실제로하고 싶은 일에 맞추기 위해 accumulate를 사용하여 어떻게 비틀려고 시도했는지 참조하십시오).

간단한 예를 들어,지도. 각 요소에 대해 키가 짝수이면 값을 출력합니다.

std::vector< std::string > valuesOfEvenKeys
    ( std::map< int, std::string > const& keyValues )
{
    std::vector< std::string > res;
    for( auto const& elem: keyValues )
    {
        if( elem.first % 2 == 0 )
        {
            res.push_back( elem.second );
        }
    }
    return res;
}         

멋지고 간단합니다. transform_if 알고리즘에 적합합니까?


표준은 중복을 최소화하는 방식으로 설계되었습니다.

이 특별한 경우 간단한 range-for 루프를 사용하여 더 읽기 쉽고 간결한 방식으로 알고리즘의 목표를 달성 할 수 있습니다.

// another way

vector<ha*> newVec;
for(auto& item : v) {
    if (item.i < 2) {
        newVec.push_back(&item);
    }
}

나는 컴파일하고 몇 가지 진단을 추가하고 OP의 알고리즘과 광산을 나란히 제시하도록 예제를 수정했습니다.

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

using namespace std;

struct ha { 
    explicit ha(int a) : i(a) {}
    int i;   // added this to solve compile error
};

// added diagnostic helpers
ostream& operator<<(ostream& os, const ha& t) {
    os << "{ " << t.i << " }";
    return os;
}

ostream& operator<<(ostream& os, const ha* t) {
    os << "&" << *t;
    return os;
}

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector
    vector<ha*> pv; // temporary vector
    // 1. 
    transform(v.begin(), v.end(), back_inserter(pv), 
        [](ha &arg) { return &arg; }); 
    // 2. 
    copy_if(pv.begin(), pv.end(), back_inserter(ph),
        [](ha *parg) { return parg->i < 2;  }); // 2. 

    // output diagnostics
    copy(begin(v), end(v), ostream_iterator<ha>(cout));
    cout << endl;
    copy(begin(ph), end(ph), ostream_iterator<ha*>(cout));
    cout << endl;


    // another way

    vector<ha*> newVec;
    for(auto& item : v) {
        if (item.i < 2) {
            newVec.push_back(&item);
        }
    }

    // diagnostics
    copy(begin(newVec), end(newVec), ostream_iterator<ha*>(cout));
    cout << endl;
    return 0;
}

오랜만에이 질문을 되살려 서 죄송합니다. 최근에 비슷한 요구 사항이있었습니다. 부스트를 취하는 back_insert_iterator 버전을 작성하여 해결했습니다.

template<class Container>
struct optional_back_insert_iterator
: public std::iterator< std::output_iterator_tag,
void, void, void, void >
{
    explicit optional_back_insert_iterator( Container& c )
    : container(std::addressof(c))
    {}

    using value_type = typename Container::value_type;

    optional_back_insert_iterator<Container>&
    operator=( const boost::optional<value_type> opt )
    {
        if (opt) {
            container->push_back(std::move(opt.value()));
        }
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator*() {
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator++() {
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator++(int) {
        return *this;
    }

protected:
    Container* container;
};

template<class Container>
optional_back_insert_iterator<Container> optional_back_inserter(Container& container)
{
    return optional_back_insert_iterator<Container>(container);
}

다음과 같이 사용 :

transform(begin(s), end(s),
          optional_back_inserter(d),
          [](const auto& s) -> boost::optional<size_t> {
              if (s.length() > 1)
                  return { s.length() * 2 };
              else
                  return { boost::none };
          });

얼마 후이 질문을 다시 찾고 잠재적으로 유용한 일반 반복기 어댑터를 모두 고안 한 후 원래 질문에는 std::reference_wrapper.

포인터 대신 사용하면 좋습니다.

Live On Coliru

#include <algorithm>
#include <functional> // std::reference_wrapper
#include <iostream>
#include <vector>

struct ha {
    int i;
};

int main() {
    std::vector<ha> v { {1}, {7}, {1}, };

    std::vector<std::reference_wrapper<ha const> > ph; // target vector
    copy_if(v.begin(), v.end(), back_inserter(ph), [](const ha &parg) { return parg.i < 2; });

    for (ha const& el : ph)
        std::cout << el.i << " ";
}

인쇄물

1 1 

copy_if함께 사용할 수 있습니다 . 왜 안돼? 정의 OutputIt( 사본 참조 ) :

struct my_inserter: back_insert_iterator<vector<ha *>>
{
  my_inserter(vector<ha *> &dst)
    : back_insert_iterator<vector<ha *>>(back_inserter<vector<ha *>>(dst))
  {
  }
  my_inserter &operator *()
  {
    return *this;
  }
  my_inserter &operator =(ha &arg)
  {
    *static_cast< back_insert_iterator<vector<ha *>> &>(*this) = &arg;
    return *this;
  }
};

코드를 다시 작성하십시오.

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector

    my_inserter yes(ph);
    copy_if(v.begin(), v.end(), yes,
        [](const ha &parg) { return parg.i < 2;  });

    return 0;
}

template <class InputIt, class OutputIt, class BinaryOp>
OutputIt
transform_if(InputIt it, InputIt end, OutputIt oit, BinaryOp op)
{
    for(; it != end; ++it, (void) ++oit)
        op(oit, *it);
    return oit;
}

사용법 : (CONDITION 및 TRANSFORM은 매크로가 아니며 적용하려는 조건 및 변환에 대한 자리 표시 자입니다.)

std::vector a{1, 2, 3, 4};
std::vector b;

return transform_if(a.begin(), a.end(), b.begin(),
    [](auto oit, auto item)             // Note the use of 'auto' to make life easier
    {
        if(CONDITION(item))             // Here's the 'if' part
            *oit++ = TRANSFORM(item);   // Here's the 'transform' part
    }
);

이것은 질문 1 "사용 가능한 C ++ 표준 라이브러리 도구에보다 우아한 해결 방법이 있습니까?"에 대한 대답입니다.

C ++ 17을 사용할 수 있다면 std::optionalC ++ 표준 라이브러리 기능 만 사용하는 더 간단한 솔루션에 사용할 수 있습니다 . 아이디어는 std::nullopt매핑이없는 경우 반환 하는 것입니다.

Coliru에서 라이브보기

#include <iostream>
#include <optional>
#include <vector>

template <
    class InputIterator, class OutputIterator, 
    class UnaryOperator
>
OutputIterator filter_transform(InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperator op)
{
    while (first1 != last1) 
    {
        if (auto mapped = op(*first1)) {
            *result = std::move(mapped.value());
            ++result;
        }
        ++first1;
    }
    return result;
}

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main()
{
    std::vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector

    // GOAL : make a vector of pointers to elements with i < 2
    std::vector<ha*> ph; // target vector
    filter_transform(v.begin(), v.end(), back_inserter(ph), 
        [](ha &arg) { return arg.i < 2 ? std::make_optional(&arg) : std::nullopt; });

    for (auto p : ph)
        std::cout << p->i << std::endl;

    return 0;
}

여기서 방금 C ++로 Rust의 접근 방식구현 했습니다.

참고 URL : https://stackoverflow.com/questions/23579832/why-is-there-no-transform-if-in-the-c-standard-library

반응형