35

隣接していない重複をほとんど含まないベクトルがあります。

簡単な例として、次のことを考慮してください。

2 1 6 1 4 6 2 1 1

vector隣接していない重複を削除し、要素の順序を維持することで、これを一意にしようとしています。

結果は次のようになります。

2 1 6 4 

私が試した解決策は次のとおりです。

  1. std::set に挿入しますが、このアプローチの問題は、要素の順序が乱れることです。
  2. std::sort と std::unique の組み合わせを使用します。しかし、再び同じ順序の問題。
  3. 手動による重複排除:

        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 アルゴリズムはありますか? その複雑さは何ですか?

4

11 に答える 11

13

一時的なものを使用せず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ます。ただし、代表的な入力を使用した測定が確実な唯一の方法です。

于 2009-09-23T08:23:18.450 に答える
13

私はあなたがこのようにするだろうと思います:

vector で 2 つの反復子を使用します。

最初のものはデータを読み取り、一時セットに挿入します。

読み取ったデータがセットにない場合は、最初のイテレータから 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 ;
        }
    }
}
于 2009-09-21T08:14:38.080 に答える
7

質問は「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。直前の要素と等しいすべての要素が削除されます (最初の要素は、何も統合できないため保持されます)。反復子は、 range 内の新しい end へのポイントを返しました[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 は vector を作成し{ 5, 5, 5 }ます。2 行目の呼び出しuniqueでは、一意でない最初の要素である 2 番目の要素に反復子を返します。したがって、 distance1 を返し resize、ベクトルを刈り込みます。

于 2012-05-09T16:29:18.063 に答える
6

次を使用して、 faの回答のループの一部を削除できますremove_copy_if

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 を使用できます。

于 2009-09-21T10:34:46.570 に答える
3

シーケンスの元の順序を維持して、必要なことを行う STL アルゴリズムはありません。

You could create a std::set of iterators or indexes into the vector, with a comparison predicate that uses the referenced data rather than the iterators/indexes to sort stuff. Then you delete everything from the vector that isn't referenced in the set. (Of course, you could just as well use another std::vector of iterators/indexes, std::sort and std::unique that, and use this as a reference as to what to keep.)

于 2009-09-21T08:44:55.647 に答える
3

@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());

このようにして、よりコンパクトになり、イテレータを気にする必要がなくなります。

パフォーマンスが大幅に低下することさえないようです。複雑な型の非常に大きなベクトルを頻繁に処理する必要があるプロジェクトで使用していますが、実際の違いはありません。

もう 1 つの優れた機能は、 を使用して一意性std::set<int, myCmp_> tmpSet;を調整できることです。たとえば、私のプロジェクトでは、特定の丸め誤差を無視します。

于 2013-09-24T14:41:45.367 に答える
2

この質問を体系的に扱っているJohnTorjoによる素晴らしい記事があります。彼が思いついた結果は、これまでにここで提案されたどのソリューションよりも一般的で効率的であるように思われます。

http://www.builderau.com.au/program/java/soa/C-Removing-duplicates-from-a-range/0,339024620,320271583,00.htm

https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1052159.html

残念ながら、ジョンのソリューションの完全なコードは利用できなくなったようで、ジョンはメールに返信しませんでした。そのため、彼と同様の根拠に基づいた独自のコードを作成しましたが、細部が意図的に異なります。お気軽に私(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
}
于 2010-02-12T09:34:05.153 に答える
2

私の質問は:

ベクトルから隣接していない重複を削除できる STL アルゴリズムはありますか? その複雑さは何ですか?

STLオプションはあなたが言及したものです:アイテムをstd::set、または callstd::sortに入れ、コンテナstd::uniqueを呼び出します。erase()これらはどちらも、「隣接していない重複を削除し、要素の順序を維持する」という要件を満たしません。

では、なぜ STL は他のオプションを提供しないのでしょうか? すべてのユーザーのニーズにすべてを提供する標準ライブラリはありません。STL の設計上の考慮事項には、「ほぼすべてのユーザーにとって十分に高速であること」、「ほぼすべてのユーザーにとって有用であること」、および「可能な限り例外の安全性を提供すること」(および「標準委員会にとって十分小さいこと」) が含まれます。書き込みははるかに大きく、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 は要素の総数です。

于 2009-09-21T08:42:34.820 に答える
1

私の知る限り、stlには何もありません。参照を検索します。

于 2009-09-21T08:47:12.930 に答える
1

@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
        } ); 
于 2013-06-21T20:53:11.357 に答える