it-swarm.com.de

Wie werden Elemente des Vektors einzigartig? (entferne nicht benachbarte Duplikate)

Ich habe einen Vektor, der einige nicht benachbarte Duplikate enthält.

Als einfaches Beispiel betrachten Sie:

2 1 6 1 4 6 2 1 1

Ich versuche, dieses vector eindeutig zu machen, indem ich die nicht benachbarten Duplikate entfernt und die Reihenfolge der Elemente beibehält.

Ergebnis wäre:

2 1 6 4 

Die Lösungen, die ich ausprobiert habe, sind:

  1. Beim Einfügen in ein std :: set ist das Problem bei diesem Ansatz jedoch, dass die Reihenfolge der Elemente gestört wird.
  2. Verwenden Sie die Kombination aus std :: sort und std :: unique. Aber wieder das gleiche Bestellproblem.
  3. Manuelle Duplikatbeseitigung:

        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.
    

Meine Frage ist:

Gibt es einen STL-Algorithmus, der die nicht benachbarten Duplikate aus dem Vektor entfernen kann? was ist die Komplexität?

34
aJ.

Ich denke, du würdest es so machen:

Ich würde zwei Iteratoren für den Vektor verwenden:

Der erste liest die Daten und fügt einen temporären Satz ein.

Wenn die gelesenen Daten nicht in der Gruppe waren, kopieren Sie sie vom ersten Iterator zum zweiten und erhöhen sie.

Am Ende bewahren Sie nur die Daten bis zum zweiten Iterator auf.

Die Komplexität ist O (n .log (n)), da bei der Suche nach duplizierten Elementen die Menge verwendet wird, nicht der Vektor.

#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 ;
        }
    }
}
13
fa.

Ohne die Verwendung einer temporären set ist dies mit (möglicherweise) etwas Leistungsverlust möglich:

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

verwendet als in:

vec.erase( Unique( vec.begin(), vec.end() ), vec.end() );

Bei kleineren Datensätzen können die einfache Implementierung und das Fehlen einer zusätzlichen Zuweisung die theoretisch höhere Komplexität der Verwendung einer zusätzlichen set ausgleichen. Die Messung mit einer repräsentativen Eingabe ist jedoch der einzige Weg, um sicher zu sein.

11
CB Bailey

Sie können einige der Schleifen in fa answer mit remove_copy_if entfernen:

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

Dies hat keinen Einfluss auf die Komplexität des Algorithmus (dh wie geschrieben, ist es auch O (n log n)). Sie können dies mit unordered_set verbessern, oder wenn der Wertebereich Ihrer Werte klein genug ist, können Sie einfach ein Array oder ein Bitarray verwenden.

6
Richard Corden

Die Frage lautete: "Gibt es einen STL-Algorithmus ...? Wie ist seine Komplexität?" Es ist sinnvoll, die Funktion wie std::unique zu implementieren:

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

Auf diese Weise wird std::unique plus ein zusätzlicher Satz implementiert. Der unordered_set soll schneller sein als eine reguläre set. Alle Elemente werden entfernt, die gleich dem vorangehenden Element verglichen werden (das erste Element wird beibehalten, da wir uns zu nichts vereinigen können). Der Iterator hat Punkte an das neue Ende innerhalb des Bereichs [first,last) zurückgegeben. 

BEARBEITEN: Der letzte Satz bedeutet, dass der Container selbst NICHT von unique geändert wird. Das kann verwirrend sein. Im folgenden Beispiel wird der Container tatsächlich auf die vereinheitlichte Menge reduziert.

1: std::vector<int> v(3, 5);
2: v.resize(std::distance(v.begin(), unique(v.begin(), v.end())));
3: assert(v.size() == 1);

Zeile 1 erzeugt einen Vektor { 5, 5, 5 }. In Zeile 2 gibt der Aufruf von unique einen Iterator an das 2nd Element zurück, das das erste Element ist, das nicht eindeutig ist. distance gibt also 1 zurück und resize beschneidet den Vektor.

6

Es gibt keinen STL-Algorithmus, der das tut, was Sie möchten, um die ursprüngliche Reihenfolge der Sequenz zu erhalten. 

Sie könnten einen std::set von Iteratoren oder Indizes in den Vektor mit einem Vergleichsprädikat erstellen, das die referenzierten Daten anstelle der Iteratoren/Indizes zum Sortieren von Daten verwendet. Dann löschen Sie alles aus dem Vektor, der nicht im Set referenziert ist. (Natürlich können Sie genauso gut einen anderen std::vector von Iteratoren/Indizes verwenden, std::sort und std::unique, und dies als Referenz für das, was Sie behalten möchten.) 

3
sbi

Basierend auf der Antwort von @fa. Es kann auch mit dem STL-Algorithmus std::stable_partition überschrieben werden:

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

Auf diese Weise ist es kompakter und wir müssen uns nicht um die Iteratoren kümmern.

Es scheint sogar, nicht zu viel Leistungsstrafe einzuführen. Ich verwende es in meinem Projekt, das oft mit ziemlich großen Vectors von komplexen Typen umgehen muss und es macht keinen wirklichen Unterschied.

Ein weiteres schönes Feature von Nizza ist, dass es möglich ist, die Eindeutigkeit mit std::set<int, myCmp_> tmpSet; anzupassen. Zum Beispiel in meinem Projekt, bestimmte Rundungsfehler zu ignorieren.

3
Heiko Schäfer

Es gibt einen netten Artikel von John Torjo, der diese Frage systematisch behandelt. Das Ergebnis scheint generischer und effizienter zu sein als alle bisher vorgeschlagenen Lösungen:

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

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

Leider ist der vollständige Code von Johns Lösung nicht mehr verfügbar, und John hat möglicherweise nicht auf E-Mails geantwortet. Daher habe ich meinen eigenen Code geschrieben, der auf ähnlichen Gründen wie sein basiert, sich aber in einigen Details absichtlich unterscheidet. Kontaktieren Sie mich (vschoech think-cell com) und besprechen Sie die Details, wenn Sie möchten.

Um den Code für Sie kompilieren zu lassen, habe ich ein paar meiner eigenen Bibliotheken hinzugefügt, die ich regelmäßig verwende. Anstelle von plain stl verwende ich auch boost, um generischeren, effizienteren und besser lesbaren Code zu erstellen.

Habe Spaß!

#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
}
2
vschoech

Meine Frage ist:

Gibt es einen STL-Algorithmus, der die nicht benachbarten Duplikate aus dem Vektor entfernen kann? was ist die Komplexität?

Die STL-Optionen sind die, die Sie erwähnt haben: Platzieren Sie die Elemente in einem std::set oder rufen Sie std::sort, std::unique und den Aufruf erase() des Containers auf. Keines davon erfüllt Ihre Anforderung, "nicht benachbarte Duplikate zu entfernen und die Reihenfolge der Elemente zu erhalten".

Warum bietet die STL keine andere Option? Keine Standardbibliothek bietet alles für jeden Benutzer. Die Design-Überlegungen der STL umfassen "Sei für fast alle Benutzer schnell genug", "Für fast alle Benutzer nützlich sein" und "Ausnahmesicherheit so weit wie möglich bieten" (und "Sei klein genug für das Normenausschuss"), wie ursprünglich die Bibliothek Stepanov war viel größer, und Stroustrup schottete etwa 2/3 davon aus).

Die einfachste Lösung, die mir einfällt, würde folgendermaßen aussehen:

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

Diese Lösung sollte O (M * N) sein, wobei M die Anzahl der eindeutigen Elemente und N die Gesamtanzahl der Elemente ist.

2
Max Lybbert

Soweit ich weiß gibt es keine in stl. Nachschlagen Referenz .

1
Yelonek

Basierend auf der Antwort von @ Corden, verwendet jedoch einen Lambda-Ausdruck und entfernt Duplikate, anstatt sie in den Ausgabevektor zu schreiben

    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
        } ); 
1
winterlight

Учитывая, что ваши входные данные находятся в vector<int> foo, вы можете использовать __ removename , чтобы выполнить работу для вас здесь, тогда, если вы хотите уменьшить вектор, просто используйте __ erasename в противном случае просто используйте lastкак Ваш итератор с одним последним концом, когда вы хотите удалите хоте

auto last = end(foo);

for(auto first = begin(foo); first < last; ++first) last = remove(next(first), last, *first);

foo.erase(last, end(foo));

Live Example

Насколько сложность времени это будет O (нм) . Где n - количество элементов, а m - число уникальных элементы. Насколько сложность пространства это будет использовать O (n) , потому что это делаее.

0
Jonathan Mee