隣接していない重複をいくつか含むベクターがあります。
簡単な例として、次のことを考慮してください。
2 1 6 1 4 6 2 1 1
隣接していない重複を削除し、要素の順序を維持することで、これをvector
に一意にしようとしています。
結果は次のようになります:
2 1 6 4
私が試した解決策は次のとおりです:
手動による重複排除:
Define a temporary vector TempVector.
for (each element in a vector)
{
if (the element does not exists in TempVector)
{
add to TempVector;
}
}
swap orginial vector with TempVector.
私の質問は:
ベクトルから隣接していない重複を削除できるSTLアルゴリズムはありますか?その複雑さは何ですか?
私はあなたがそれをこのようにするだろうと思います:
私はベクターに2つのイテレータを使います:
最初の1つはデータを読み取り、一時セットに挿入します。
読み取ったデータがセット内になかった場合は、最初のイテレーターから2番目のイテレーターにコピーして、増分します。
最後に、データのみを2番目のイテレータまで保持します。
複製された要素の検索ではベクトルではなくセットを使用するため、複雑度はO(n .log(n))です。
#include <vector>
#include <set>
#include <iostream>
int main(int argc, char* argv[])
{
std::vector< int > k ;
k.Push_back( 2 );
k.Push_back( 1 );
k.Push_back( 6 );
k.Push_back( 1 );
k.Push_back( 4 );
k.Push_back( 6 );
k.Push_back( 2 );
k.Push_back( 1 );
k.Push_back( 1 );
{
std::vector< int >::iterator r , w ;
std::set< int > tmpset ;
for( r = k.begin() , w = k.begin() ; r != k.end() ; ++r )
{
if( tmpset.insert( *r ).second )
{
*w++ = *r ;
}
}
k.erase( w , k.end() );
}
{
std::vector< int >::iterator r ;
for( r = k.begin() ; r != k.end() ; ++r )
{
std::cout << *r << std::endl ;
}
}
}
一時的なset
を使用しないと、(おそらく)パフォーマンスが低下する可能性があります。
template<class Iterator>
Iterator Unique(Iterator first, Iterator last)
{
while (first != last)
{
Iterator next(first);
last = std::remove(++next, last, *first);
first = next;
}
return last;
}
次のように使用されます:
vec.erase( Unique( vec.begin(), vec.end() ), vec.end() );
小さいデータセットの場合、実装が単純で、追加の割り当てが必要ないため、追加のset
を使用することの理論的により高い複雑さが相殺される場合があります。ただし、代表的な入力を使用した測定が確実な唯一の方法です。
質問は「STLアルゴリズムはありますか...?その複雑さは何ですか?」 std::unique
のような関数を実装することは理にかなっています:
template <class FwdIterator>
inline FwdIterator stable_unique(FwdIterator first, FwdIterator last)
{
FwdIterator result = first;
std::unordered_set<typename FwdIterator::value_type> seen;
for (; first != last; ++first)
if (seen.insert(*first).second)
*result++ = *first;
return result;
}
したがって、これがstd::unique
の実装方法と追加のセットです。 unordered_set
は通常のset
より高速です。直前の要素と等しいすべての要素が削除されます(何にも統合できないため、最初の要素が保持されます)。返されたイテレータは、[first,last)
の範囲内の新しい終端を指します。
編集:最後の文は、コンテナ自体がunique
によって変更されていないことを意味します。これは混乱を招く可能性があります。次の例では、実際にコンテナを統合セットに削減しています。
1: std::vector<int> v(3, 5);
2: v.resize(std::distance(v.begin(), unique(v.begin(), v.end())));
3: assert(v.size() == 1);
1行目は、ベクトル{ 5, 5, 5 }
を作成します。 2行目でunique
を呼び出すと、一意でない最初の要素である2番目の要素への反復子が返されます。したがって、distance
は1を返し、resize
はベクトルをプルーニングします。
remove_copy_if
を使用して fa's answerのループの一部を削除できます:
class NotSeen : public std::unary_function <int, bool>
{
public:
NotSeen (std::set<int> & seen) : m_seen (seen) { }
bool operator ()(int i) const {
return (m_seen.insert (i).second);
}
private:
std::set<int> & m_seen;
};
void removeDups (std::vector<int> const & iv, std::vector<int> & ov) {
std::set<int> seen;
std::remove_copy_if (iv.begin ()
, iv.end ()
, std::back_inserter (ov)
, NotSeen (seen));
}
これはアルゴリズムの複雑さには影響しません(つまり、書かれているようにO(n log n)でもあります)。 unordered_setを使用してこれを改善できます。または、値の範囲が十分に小さい場合は、単純に配列またはbitarrayを使用できます。
シーケンスの元の順序を維持したいことを行うSTLアルゴリズムはありません。
std::set
のベクトルへのイテレータまたはインデックス。イテレータ/インデックスではなく参照されたデータを使用してデータをソートする比較述語。次に、セットで参照されていないベクターからすべてを削除します。 (もちろん、別のstd::vector
/イテレータ/インデックス、std::sort
およびstd::unique
それ、そして何を保持するかについての参照としてこれを使用してください。)
@faの回答に基づく。また、STLアルゴリズムstd::stable_partition
を使用して書き換えることもできます。
struct dupChecker_ {
inline dupChecker_() : tmpSet() {}
inline bool operator()(int i) {
return tmpSet.insert(i).second;
}
private:
std::set<int> tmpSet;
};
k.erase(std::stable_partition(k.begin(), k.end(), dupChecker_()), k.end());
これにより、よりコンパクトになり、イテレータを気にする必要がなくなります。
それは多くのパフォーマンスのペナルティを導入しないようにも見えます。私は、非常に大きなvectorsの複雑な型を頻繁に処理する必要があるプロジェクトで使用し、実際の違いはありません。
もう1つの素晴らしい機能は、std::set<int, myCmp_> tmpSet;
を使用してniquenessを調整できることです。たとえば、私のプロジェクトでは、特定の丸めエラーを無視します。
John Torjoによる素晴らしい記事があり、この質問そのものを体系的に扱います。彼が思いついた結果は、これまでに提案されているどのソリューションよりも一般的で効率的です。
https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1052159.html
残念ながら、Johnのソリューションの完全なコードはもう利用できなくなっているようで、Johnはメールに応答しませんでした。したがって、私は彼と同じような根拠に基づいて独自のコードを記述しましたが、意図的に細部が異なります。必要に応じて、私(vschoech think-cell com)に連絡し、詳細についてお気軽にご相談ください。
コードをコンパイルできるようにするために、私が定期的に使用する独自のライブラリー要素をいくつか追加しました。また、単純なstlを使用する代わりに、boostを使用して、より汎用的で効率的で読みやすいコードを作成します。
楽しんで!
#include <vector>
#include <functional>
#include <boost/bind.hpp>
#include <boost/range.hpp>
#include <boost/iterator/counting_iterator.hpp>
/////////////////////////////////////////////////////////////////////////////////////////////
// library stuff
template< class Rng, class Func >
Func for_each( Rng& rng, Func f ) {
return std::for_each( boost::begin(rng), boost::end(rng), f );
};
template< class Rng, class Pred >
Rng& sort( Rng& rng, Pred pred ) {
std::sort( boost::begin( rng ), boost::end( rng ), pred );
return rng; // to allow function chaining, similar to operator+= et al.
}
template< class T >
boost::iterator_range< boost::counting_iterator<T> > make_counting_range( T const& tBegin, T const& tEnd ) {
return boost::iterator_range< boost::counting_iterator<T> >( tBegin, tEnd );
}
template< class Func >
class compare_less_impl {
private:
Func m_func;
public:
typedef bool result_type;
compare_less_impl( Func func )
: m_func( func )
{}
template< class T1, class T2 > bool operator()( T1 const& tLeft, T2 const& tRight ) const {
return m_func( tLeft ) < m_func( tRight );
}
};
template< class Func >
compare_less_impl<Func> compare_less( Func func ) {
return compare_less_impl<Func>( func );
}
/////////////////////////////////////////////////////////////////////////////////////////////
// stable_unique
template<class forward_iterator, class predicate_type>
forward_iterator stable_unique(forward_iterator itBegin, forward_iterator itEnd, predicate_type predLess) {
typedef std::iterator_traits<forward_iterator>::difference_type index_type;
struct SIteratorIndex {
SIteratorIndex(forward_iterator itValue, index_type idx) : m_itValue(itValue), m_idx(idx) {}
std::iterator_traits<forward_iterator>::reference Value() const {return *m_itValue;}
index_type m_idx;
private:
forward_iterator m_itValue;
};
// {1} create array of values (represented by iterators) and indices
std::vector<SIteratorIndex> vecitidx;
vecitidx.reserve( std::distance(itBegin, itEnd) );
struct FPushBackIteratorIndex {
FPushBackIteratorIndex(std::vector<SIteratorIndex>& vecitidx) : m_vecitidx(vecitidx) {}
void operator()(forward_iterator itValue) const {
m_vecitidx.Push_back( SIteratorIndex(itValue, m_vecitidx.size()) );
}
private:
std::vector<SIteratorIndex>& m_vecitidx;
};
for_each( make_counting_range(itBegin, itEnd), FPushBackIteratorIndex(vecitidx) );
// {2} sort by underlying value
struct FStableCompareByValue {
FStableCompareByValue(predicate_type predLess) : m_predLess(predLess) {}
bool operator()(SIteratorIndex const& itidxA, SIteratorIndex const& itidxB) {
return m_predLess(itidxA.Value(), itidxB.Value())
// stable sort order, index is secondary criterion
|| !m_predLess(itidxB.Value(), itidxA.Value()) && itidxA.m_idx < itidxB.m_idx;
}
private:
predicate_type m_predLess;
};
sort( vecitidx, FStableCompareByValue(predLess) );
// {3} apply std::unique to the sorted vector, removing duplicate values
vecitidx.erase(
std::unique( vecitidx.begin(), vecitidx.end(),
!boost::bind( predLess,
// redundand boost::mem_fn required to compile
boost::bind(boost::mem_fn(&SIteratorIndex::Value), _1),
boost::bind(boost::mem_fn(&SIteratorIndex::Value), _2)
)
),
vecitidx.end()
);
// {4} re-sort by index to match original order
sort( vecitidx, compare_less(boost::mem_fn(&SIteratorIndex::m_idx)) );
// {5} keep only those values in the original range that were not removed by std::unique
std::vector<SIteratorIndex>::iterator ititidx = vecitidx.begin();
forward_iterator itSrc = itBegin;
index_type idx = 0;
for(;;) {
if( ititidx==vecitidx.end() ) {
// {6} return end of unique range
return itSrc;
}
if( idx!=ititidx->m_idx ) {
// original range must be modified
break;
}
++ititidx;
++idx;
++itSrc;
}
forward_iterator itDst = itSrc;
do {
++idx;
++itSrc;
// while there are still items in vecitidx, there must also be corresponding items in the original range
if( idx==ititidx->m_idx ) {
std::swap( *itDst, *itSrc ); // C++0x move
++ititidx;
++itDst;
}
} while( ititidx!=vecitidx.end() );
// {6} return end of unique range
return itDst;
}
template<class forward_iterator>
forward_iterator stable_unique(forward_iterator itBegin, forward_iterator itEnd) {
return stable_unique( itBegin, itEnd, std::less< std::iterator_traits<forward_iterator>::value_type >() );
}
void stable_unique_test() {
std::vector<int> vecn;
vecn.Push_back(1);
vecn.Push_back(17);
vecn.Push_back(-100);
vecn.Push_back(17);
vecn.Push_back(1);
vecn.Push_back(17);
vecn.Push_back(53);
vecn.erase( stable_unique(vecn.begin(), vecn.end()), vecn.end() );
// result: 1, 17, -100, 53
}
私の質問は:
ベクトルから隣接していない重複を削除できるSTLアルゴリズムはありますか?その複雑さは何ですか?
STLオプションはあなたが言及したものです:アイテムを_std::set
_に置くか、または_std::sort
_、_std::unique
_を呼び出し、コンテナーでerase()
を呼び出します。これらはどちらも、「隣接していない重複を削除して要素の順序を維持する」という要件を満たしていません。
では、なぜSTLが他のオプションを提供しないのですか?すべてのユーザーのニーズに対応する標準ライブラリはありません。 STLの設計上の考慮事項には、「ほぼすべてのユーザーにとって十分な速さ」、「ほとんどすべてのユーザーにとって有用」、「例外の安全性を可能な限り提供する」(およびライブラリStepanovとして「標準委員会にとって十分に小さい」)が含まれます。書いたものははるかに大きく、Stroustrupはその2/3のようなものを削除しました)。
私が考えることができる最も簡単な解決策は次のようになります:
_// Note: an STL-like method would be templatized and use iterators instead of
// hardcoding std::vector<int>
std::vector<int> stable_unique(const std::vector<int>& input)
{
std::vector<int> result;
result.reserve(input.size());
for (std::vector<int>::iterator itor = input.begin();
itor != input.end();
++itor)
if (std::find(result.begin(), result.end(), *itor) == result.end())
result.Push_back(*itor);
return result;
}
_
この解はO(M * N)である必要があります。ここで、Mは一意の要素の数、Nは要素の総数です。
私が知る限り、stlには何もありません。検索 参照 。
@Cordenの回答に基づいていますが、ラムダ式を使用し、出力ベクトルに書き込む代わりに重複を削除します
set<int> s;
vector<int> nodups;
remove_copy_if(v.begin(), v.end(), back_inserter(nodups),
[&s](int x){
return !s.insert(x).second; //-> .second false means already exists
} );
あなたの入力がvector<int> foo
remove
を使用してここで脚の作業を行うことができます。次に、ベクトルを縮小する場合は erase
を使用しますそれ以外の場合は、重複を削除して順序を維持したベクトルが必要な場合は、last
を最後までの反復子として使用します。
auto last = end(foo);
for(auto first = begin(foo); first < last; ++first) last = remove(next(first), last, *first);
foo.erase(last, end(foo));
時間の複雑さに関する限り、これはO(nm)になります。ここで、nは要素の数であり、mは一意の数です要素。スペースの複雑さに関する限り、これはO(n)を使用します。これは、削除が適切に行われるためです。