Efficient way to get unique elements from a vector of pairs - c++

I have a vector of pairs of integers that looks somehow like that:
(0, 1)
(1, 9)
(2, 3)
(6, 1)
(4, 0)
I want to extract unique elements from there, so that the result looks as follows:
‍‍0‍, 1, 9, 2, 3, 6, 4
(basically just all numbers without duplicates)
At the moment I'm doing it like that:
std::vector<int> getElements(std::vector<std::pair<int, int>> S) {
std::vector<int> V;
for (std::vector<std::pair<int, int>>::iterator i = S.begin(); i != S.end(); i++) {
if (std::find(V.begin(), V.end(), i->first) == V.end()) {
V.push_back(i->first);
}
if (std::find(V.begin(), V.end(), i->second) == V.end()) {
V.push_back(i->second);
}
}
return V;
}
Is there any more efficient way to do it?

Your current solution is O(n^2). You can reduce the linear-scan for already seen elements to an amortized O(1) by using std::unordered_set to store the already seen numbers; This will improve your runtime to O(n).
Here is an improved algorithm:
std::vector<int> getElements(std::vector<std::pair<int, int>> S) {
std::unordered_set<int> ss;
std::for_each(S.begin(), S.end(), [&ss](const auto& p) {
ss.insert(p.first);
ss.insert(p.second);
});
return std::vector<int>(ss.begin(), ss.end());
}
See an example Live On Coliru

Is there any more efficient way to do it?
Yes, there is. std::find has O(n) complexity for vector, so repeating it for each element gives you O(n*n) complexity.
A simple alternative is to add every element into std::set. The complexity of building the set is O(n log n).

Not measured, but I think it is faster...
#include <iostream>
#include <algorithm>
#include <vector>
std::vector<int> getElements(std::vector<std::pair<int, int>>& S) {
std::vector<int> V;
V.reserve(2*S.size());
for (const auto& i : S) {
V.push_back(i.first);
V.push_back(i.second);
}
std::sort(V.begin(), V.end());
V.erase(std::unique(V.begin(), V.end()), V.end());
return V;
}
int main()
{
std::vector<std::pair<int, int>> v{{0, 1},{1, 9},{2, 3},{6, 1},{4, 0}};
for(const auto& i : getElements(v))
std::cout << i << ' ';
std::cout << '\n';
}

Related

How do I use std::copy_if by both coping elements and removing those elements from the original container

Say I have a std::vector<int> with a simple operation to copy those elements which are even:
#include <vector>
int main()
{
std::vector<int> v = {1, 2, 3, 4, 5, 6};
std::vector<int> even;
std::copy_if(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()), std::back_inserter(even), [](int i){return i%2 == 0;});
return 0;
}
My question how can I combine the above with any other method to remove the elements from vector v which was copied to vector even
I wouldn't recommend trying to use std::copy_if here. Instead use std::stable_partition to move even elements to the end of v, copy this part to even using the vector constructor and then erase the copied elements from v:
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
// actual logic
auto pivot = std::stable_partition(v.begin(), v.end(), [](int x) { return (x % 2) != 0; });
std::vector<int> even(pivot, v.end());
v.erase(pivot, v.end());
// display results
std::cout << "v:\n";
for (auto x : v)
{
std::cout << x << '\n';
}
std::cout << "even:\n";
for (auto x : even)
{
std::cout << x << '\n';
}
return 0;
}
For objects that are expensive to copy, you may want to use std::move_iterator when creating even as suggested in #MooningDucks answer:
std::vector<int> even(std::move_iterator(pivot), std::move_iterator(v.end()));
I would switch to using std::remove_if instead and have the function you pass to remove_if do the adding to the other vector. That gives you
std::vector<int> v = {1, 2, 3, 4, 5, 6};
std::vector<int> even;
v.erase(std::remove_if(v.begin(), v.end(),
[&](auto val){ bool cond = (val % 2 == 0);
if (cond) even.push_back(val); return cond; }), v.end());

inserter iterator to insert in different container depending on predicate

Lets say I have a vector of numbers and I want to create two vectors to separate the numbers on odd and even ones. Easily with a simple for:
std::vector<int> odds;
std::vector<int> evens;
std::vector<int> numbers;
for (int number : numbers) {
if isOdd(number)
odds.push_back(number);
else
evens.push_back(number);
}
I would like to know if there any kind of inserter_iterator that can manage to do this so i can write something like
std::vector<int> odds;
std::vector<int> evens;
std::vector<int> numbers;
auto pred = [](int i) { return isOdd(i) ? True : False;};
auto identity = [](int i) {return i;};
std::transform(std::begin(numbers), std::end(numbers), some_inserter(odd, evens, pred), identity);
This is just out of curiosity, I'm trying to learn how algorithms and iterators work. A solution based on ranges is also valid.
You can use std::partition_copy algorithm, as follows https://en.cppreference.com/w/cpp/algorithm/partition_copy
int main() {
std::vector vec = {1, 2, 3, 4, 5, 6};//the vector to be partitioned
std::vector<int> odds(vec.size());//will hold odds (to have the sam size as the original (the maximum case) )
std::vector<int> evens(vec.size());//will hold evens
auto [oddsEnd, evensEnd] =
std::partition_copy(vec.begin(), vec.end(), odds.begin(), evens.begin(), [](int i){return i%2!=0;});//will copy at the front (won't insert at the back)
odds.erase(oddsEnd, odds.end());//to erase the undesired excess
evens.erase(evensEnd, evens.end());//to erase the undesired excess
}
OR use std::back_inserter https://en.cppreference.com/w/cpp/iterator/back_inserter (See the comments)
int main() {
std::vector vec = {1, 2, 3, 4, 5, 6};//the vector to be partitioned
std::vector<int> odds;//will hold odds
std::vector<int> evens;//will hold evens
std::partition_copy(vec.begin(), vec.end(), std::back_inserter(odds), std::back_inserter(evens), [](int i){return i%2!=0;});//will insert
}
You can simply use for_each which can be used almost like you described:
std::vector<int> odds;
std::vector<int> evens;
std::vector<int> numbers;
auto pred = [](int i) { return i % 2;};
auto identity = [](int i) {return i;};
std::for_each(std::begin(numbers), std::end(numbers), [&](int number) mutable {
int iden = identity(number);
if (pred(iden)) odds.push_back(iden);
else evens.push_back(iden);
});
// Or with range-based loop:
for (auto &elem : numbers) {
if (pred(iden)) odds.push_back(iden);
else evens.push_back(iden);
}

How to select the most common numbers from a vector or on array?(TOP5 toplist) C++

I have a vector with integers. I would like to select the most common numbers. I would like to make a top 5 list. For example:
std::vector<int> numbers = {32, 32, 32, 12, 12, 11, 11, 11, 9};
the most common numbers are: TOP 1: 32, TOP 2: 11, TOP 3: 12, TOP 4: 9;
after I selected them, I would like to store it into an other vector: the most common numbers.
Here's another algorithm, the cost is going to be O(n) for any k, plus a decent cache locality.
1.First store all elements in a unordered_map O(N)
std::unordered_map<int, int> m;
for(const auto ele: numbers) {
m[ele]++;
}
2.Dump all elements in a vector of pairs O(N)
std::vector<std::pair<int, int>> temp;
for(const auto& ele: m) {
temp.emplace_back(ele.first , ele.second);
}
3.Now use the nth_element to find kth rank O(N)
std::nth_element( temp.begin(), temp.begin()+k ,
temp.end(), [](const auto& p1, const auto& p2) {
// Strict weak ordering
if (p1.second > p2.second) {
return true;
} if (p1.second < p2.second) {
return false;
}
return p1.first > p2.first; // We have to print large element first
} );
4.Display the output
std::for_each( temp.begin(), temp.begin() +k - 1, [](const auto & p) {
std::cout << p.first << " ";
});
Demo Here
You can create a unordered_map<int,int> mp where you can store the count of each number, like mp[32] = 3.
Next you need to find the top five elements
Time Complexity : O(mlogm) : You can sort the map in descending order( to sort it you will have to use a extra vector), and take the first 5 elements.
Time Complexity: O(m) : Or you can iterate 5 times over the whole map to get the top file elements. Each time you iterate find the number with the greatest frequency which is not present in our topFive vector yet.
m : number of entries in the map.
I've made this example and put comments in-line. It needs C++11 at least.
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
int main(void) {
std::map<int, int> ans;
std::vector<int> numbers = {32, 32, 32, 12, 12, 11, 11, 11, 9};
std::vector<std::pair<int, int>> sorted;
std::vector<int> common;
// Step 1 Accumulate into a map, counting occurrences
for (auto number : numbers) {
ans[number]++;
}
// Step 2 Make a linear list, putting the count first then the value
for (auto& ent : ans) {
sorted.emplace_back(ent.second, ent.first);
}
// Step 3 sort it, by count ascending
std::sort(std::begin(sorted), std::end(sorted));
// Step 4 Get commonest 5 (or fewer)
for (int i = 1; i<= 5; ++i) {
int index = sorted.size() - i;
if (index >= 0) {
common.push_back(sorted[index].second);
}
}
// Step 5 print out
for (auto i : common) {
std::cout << i << std::endl;
}
return 0;
}
you can do this: create a set so you get rid off duplicated, then find the frequency of every item of the set in the vector, with this result create a pair (something like int, int) push the pair in a vector and finally sort it using your own predicate:
now for the top x you can do a for loop or just resize the vector if you are sure what the consequences of this are.
std::vector<int> numbers{32, 32, 32, 12, 12, 11, 11, 11, 9};
std::set<int> mySet(numbers.begin(), numbers.end());
std::vector<std::pair<int, int>> result{};
for(const auto& x : mySet)
{
result.push_back(std::make_pair(x , std::count(numbers.begin(), numbers.end(), x)));
}
std::sort(result.begin(), result.end(), [](const std::pair<int, int>& a, const std::pair<int, int>& b){return (b.second < a.second);});
//result.resize(3);
std::cout << "Top: " << std::endl;
for(const auto& x : result)
{
std::cout << x.first << ':' << x.second << std::endl;
}
the result will be:
Top: 11:3 32:3 12:2 9:1
There are many many ways how to achieve this. One of which may be.
std::vector numbers = {32, 32, 32, 12, 12, 11, 11, 11, 9};
int maxNumber = *std::max_element(numbers.begin(), numbers.end())
std::vector<int> occurrences(maxNumber + 1, 0);
for(auto& value : numbers)
{
occurrences[value]++;
}
Then you just need to sort the array whilst keeping track of the indexes. This is a topic of another question C++ sorting and keeping track of indexes
.

Finding elements of std::vector in std::set

I have two containers std::set and std::vector and my task is to return elements from std::vector which are present in std::set. What is the most effective way to achieve it?
Simple solution:
Iterate through elements of vector and call set.find on each and then vector.erase if not found.
How about just looking for every element? If your vector is not sorted, then there's no way around n log(n)
#include <algorithm>
std::vector<int> result;
for(auto&& el: myvector) {
auto it_found = myset.find(el);
if(it != myset.end())
result.push_back(*it_found);
}
Now result has all the elements that are in both.
PS: Haven't compiled the code, there might be slight errors.
You can use more STL :)
#include <algorithm>
#include <set>
#include <vector>
#include <iostream>
#include <iterator>
int main() {
std::vector<int> v {5, 4, 3, 2, 1};
std::set<int> s {1, 3, 5};
v.erase(std::remove_if(v.begin(), v.end(),
[&s](int a) { return s.find(a) == s.end(); }),
v.end());
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
}
Shortest way is probably to use std::set_intersection. But you should sort a vector to make it work:
int main()
{
std::set<int> s{1,2,3,4,5,6,7,8};
std::vector<int> v{7,5,10,9};
std::sort(v.begin(), v.end()); // should not bother you if vector is small
std::vector<int> intersection;
std::set_intersection(s.begin(), s.end(), v.begin(), v.end(), std::back_inserter(intersection));
for(int n : intersection)
std::cout << n << ' ';
}
Prints: 5 7
Depending on relative size of set and vector, remove_if may be the right thing...
#include <set>
#include <vector>
#include <iostream>
#include <algorithm>
int main()
{
std::set<int> s{1,2,3,4,5,6,7,8};
std::vector<int> v{7,5,10,9};
v.erase(std::remove_if(v.begin(), v.end(), [&](int e){return s.count(e) == 0;}), v.end());
for(int n : v)
std::cout << n << ' ';
}
If you look for the most cpu-effective way of doing this in terms of complexity, have additional memory and a good hash function, you can do it in O(n + m):
std::vector<int> v;
std::set<int> s;
std::unordered_set<int> us{s.cbegin(), s.cend(), s.size()};
v.erase(
std::remove_if(v.begin(), v.end(),
[&us] (const int entry) { return us.find(entry) == us.cend(); }),
v.end());
Explanation: you iterate over your set once (O(m)) to prepare unordered_set. Then you iterate through your vector once (O(n)), performing unordered_set::find (0(1)) each step. It gives you the resulting complexity of O(n+m).
Also, the size of unordered_set being equal to the size of set and a good hash function helps to reduce constant part in complexity of std::unordered_set::find.
See live example.
Bear in mind, however, that lower complexity doesn't necessarily mean faster execution in particular circumstances (for example, because of extra allocations).

Generating all possible combination of the elements of integer vector in c++

I have an integer vector of size "n".
e.g.
std::vector<int> vec;
vec.pushback(0);
vec.pushback(1);
vec.pushback(2);
vec.pushback(3);
....
Now i want to generate all possible combinations of size = {0, 1, 2, ... , n}.
please keep in mind that
{0, 1, 3} is not equal to {3, 1, 0} or {1, 0, 3} or {3, 0, 1}
Please help me if you have an idea.
Thanks in advance.
You could do something like this:
#include<iostream>
#include<algorithm>
#include <vector>
std::ostream& operator<< (std::ostream& os, std::vector<int> v)
{
for (auto it = v.begin(); it != v.end(); ++it)
{
os << *it << ",";
}
return os;
}
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
do {
std::cout << v;
} while (std::next_permutation(v.begin(), v.end()));
}
As per #JamesKanze's comment, this can only work if the vector is sorted to begin with, so if you have an unsorted vector, you should call std::sort on it first.
Looking at this, it says:
Transforms the range [first, last) into the next permutation from the set of all permutations that are lexicographically ordered with respect to operator< or comp.
You can see it in action here