Keep the duplicated values only - Vectors C++

Viewed 628

Assume I have a vector with the following elements {1, 1, 2, 3, 3, 4} I want to write a program with c++ code to remove the unique values and keep only the duplicated once. So the end result will be something like this {1,3}.

So far this is what I've done, but it takes a lot of time, Is there any way this can be more efficient,

vector <int> g1 = {1,1,2,3,3,4}
vector <int> g2;

for(int i = 0; i < g1.size(); i++)
{
  if(count(g1.begin(), g1.end(), g1[i]) > 1)
    g2.push_back(g1[i]);

}

v.erase(std::unique(g2.begin(), g2.end()), g2.end());

for(int i = 0; i < g2.size(); i++)
{
  cout << g2[i];
}
6 Answers

My approach is to create an <algorithm>-style template, and use an unordered_map to do the counting. This means you only iterate over the input list once, and the time complexity is O(n). It does use O(n) extra memory though, and isn't particularly cache-friendly. Also this does assume that the type in the input is hashable.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <unordered_map>

template <typename InputIt, typename OutputIt>
OutputIt copy_duplicates(
        InputIt  first,
        InputIt  last,
        OutputIt d_first)
{
    std::unordered_map<typename std::iterator_traits<InputIt>::value_type,
                       std::size_t> seen;
    for ( ; first != last; ++first) {
        if ( 2 == ++seen[*first] ) {
            // only output on the second time of seeing a value
            *d_first = *first;
            ++d_first;
        }
    }
    return d_first;
}

int main()
{
    int i[] = {1, 2, 3, 1, 1, 3, 5}; // print 1, 3,
    //                  ^     ^
    copy_duplicates(std::begin(i), std::end(i),
                    std::ostream_iterator<int>(std::cout, ", "));
}

This can output to any kind of iterator. There are special iterators you can use that when written to will insert the value into a container.

Here's a way that's a little more cache friendly than unordered_map answer, but is O(n log n) instead of O(n), though it does not use any extra memory and does no allocations. Additionally, the overall multiplier is probably higher, in spite of it's cache friendliness.

#include <vector>
#include <algorithm>

void only_distinct_duplicates(::std::vector<int> &v)
{
    ::std::sort(v.begin(), v.end());
    auto output = v.begin();
    auto test = v.begin();
    auto run_start = v.begin();
    auto const end = v.end();
    for (auto test = v.begin(); test != end; ++test) {
       if (*test == *run_start) {
           if ((test - run_start) == 1) {
              *output = *run_start;
              ++output;
           }
       } else {
           run_start = test;
       }
    }
    v.erase(output, end);
}

I've tested this, and it works. If you want a generic version that should work on any type that vector can store:

template <typename T>
void only_distinct_duplicates(::std::vector<T> &v)
{
    ::std::sort(v.begin(), v.end());
    auto output = v.begin();
    auto test = v.begin();
    auto run_start = v.begin();
    auto const end = v.end();
    for (auto test = v.begin(); test != end; ++test) {
       if (*test != *run_start) {
           if ((test - run_start) > 1) {
              ::std::swap(*output, *run_start);
              ++output;
           }
           run_start = test;
       }
    }
    if ((end - run_start) > 1) {
        ::std::swap(*output, *run_start);
        ++output;
    }
    v.erase(output, end);
}

Assuming the input vector is not sorted, the following will work and is generalized to support any vector with element type T. It will be more efficient than the other solutions proposed so far.

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

template<typename T>
void erase_unique_and_duplicates(std::vector<T>& v)
{
  auto first{v.begin()};
  std::sort(first, v.end());
  while (first != v.end()) {
    auto last{std::find_if(first, v.end(), [&](int i) { return i != *first; })};
    if (last - first > 1) {
      first = v.erase(first + 1, last);
    }
    else {
      first = v.erase(first);
    }
  }
}

int main(int argc, char** argv)
{
  std::vector<int> v{1, 2, 3, 4, 5, 2, 3, 4};
  erase_unique_and_duplicates(v);

  // The following will print '2 3 4'.
  for (int i : v) {
    std::cout << i << ' ';
  }
  std::cout << '\n';

  return 0;
}

I have 2 improvements for you:

  • You can change your count to start at g1.begin() + i, everything before was handled by the previous iterations of the loop.

  • You can change the if to == 2 instead of > 1, so it will add numbers only once, independent of the occurences. If a number is 5 times in the vector, the first 3 will be ignored, the 4th will make it into the new vector and the 5th will be ignored again. So you can remove the erase of the duplicates

Example:

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

using namespace std;

int main() {
    vector <int> g1 = {1,1,2,3,3,1,4};
    vector <int> g2;

    for(int i = 0; i < g1.size(); i++)
    {
      if(count(g1.begin() + i, g1.end(), g1[i]) == 2)
        g2.push_back(g1[i]);
    }

    for(int i = 0; i < g2.size(); i++)
    {
      cout << g2[i] << " ";
    }
    cout << endl;
    return 0;
}

I'll borrow a principal from Python which is excellent for such operations -

You can use a dictionary where the dictionary-key is the item in the vector and the dictionary-value is the count (start with 1 and increase by one every time you encounter a value that is already in the dictionary).

afterward, create a new vector (or clear the original) with only the dictionary keys that are larger than 1.

Look up in google - std::map

Hope this helps.

In general, that task got complexity about O(n*n), that's why it appears slow. Does it have to be a vector? Is that a restriction? Must it be ordered? If not, it better to actually store values as std::map, which eliminates doubles when populated, or as a std::multimap if presence of doubles matters.

Related