Correct usage of C++ STL set_symmetric_difference? - c++

In C++ I want to take two STL sets and produce a set which contains all the elements that are not in both sets, using set_symmetric_difference in algorithm.
I am getting a compile error "'_UDest': you cannot assign to a variable that is const", so I am misunderstanding how to use set_symmetric_difference.
Here is the code (C++ compiler is Visual Studio 2019):
set<unsigned short> a, b, diff;
// code to add entries to a and b
set_symmetric_difference(a.begin(), a.end(), b.begin(), b.end(), diff.begin()); // error C3892: '_UDest': you cannot assign to a variable that is const

You're passing an iterator into the empty set diff, as your "output iterator". Iterators don't work like this. diff.begin() refers to existing elements (of which there are none).
Set elements are immutable (they have to be, in order to maintain their ordering invariant), and that's why the particular error message you get relates to constness. However, without this factor, you'd still be trying to write into elements that don't exist.
You can instead use the special std::inserter magic iterator to wrap insertions into diff, like in the set_symmetric_difference usage example on cppreference (though that one uses sorted std::vector "sets" rather than std::set, so it gets to use std::back_inserter).
Here's an example:
std::set<unsigned short> a, b, diff;
// (code to add entries to a and b)
std::set_symmetric_difference(
a.begin(), a.end(),
b.begin(), b.end(),
std::inserter(diff, diff.begin()) // <---
);
You'll need #include <iterator> for this.

Related

How to get (unordered) set difference or symmetric difference in C++?

I can't infer I can use std::set_difference from documentation, because it says sets should be ordered, which means they are not sets, but lists. Also all examples are about ordered lists, not sets.
How to know the truth?
std::set_difference is for use with arbitrary sorted inputs (pre-sorted std::vectors, std::lists, std::deques, plain array, etc.), it just happens to work with std::set (which is sorted) too.
If you're working with std::unordered_set (or std::set, and you're okay with operating in place), you'd just use the erase method to remove all elements from one such set from another to get the difference, e.g.:
for (const auto& elem : set_to_remove) {
myset.erase(elem);
}
You can also do it into a new set with std::copy_if; the recipe there is trivially adaptable to the case of symmetric difference (it's just two calls to std::copy_if, where each one runs on one input set, and is conditioned on the element not existing in other input set).
std::set is sorted. Check out the docs:
std::set is an associative container that contains a sorted set of
unique objects of type Key. Sorting is done using the key comparison
function Compare. Search, removal, and insertion operations have
logarithmic complexity. Sets are usually implemented as red-black
trees.
Therefore, you can use it in a same way as any other container that provides the required interface. The difference between std::set and e.g. std::vector is that std::set is sorting its elements on insertion and in case of std::vector you need to use std::sort function to get its elements sorted.
For example, if you need to std::set_difference for std::unordered_set, you can do it like this:
#include <set>
#include <iostream>
#include <algorithm>
#include <unordered_set>
int main() {
std::unordered_set<int> a {3, 1, 4, 6, 5, 9};
std::unordered_set<int> b {3, 1, 4};
std::set<int> c;
std::set<int> d;
std::copy(a.begin(), a.end(), std::inserter(c, c.end()));
std::copy(b.begin(), b.end(), std::inserter(d, d.end()));
std::vector<int> diff;
std::set_difference(c.begin(), c.end(), d.begin(), d.end(),
std::inserter(diff, diff.begin()));
for (auto const i : diff)
std::cout << i << ' ';
return 0;
}
See live

Assignment of read-only location while using set_union to merge two sets

I am trying to combine two set to one set, but when I use the simplest example, a error:assignment of read-only location '__result.std::_Rb_tree_const_iterator<_Tp>::operator*<int>()' the code is:
set<int> a;
set<int> b;
int x[4] = {0,1,2,3};int y[5] = {1,2,4,6,9};
a.insert(x,x+4);
b.insert(y,y+5);
set<int> c;
set_union(a.begin(), a.end(), b.begin(), b.end(), c.begin());
So am I writing wrong? What should I do if I want to merge two set and use a new set to contain the elements?
The error calls from this line: set_union(a.begin(), a.end(), b.begin(), b.end(), c.begin());
std::set<int>::iterator isn't an OutputIterator, so it isn't suitable for use as the fifth argument of std::set_union. You probably mean to insert into c, so a suitable iterator is std::inserter(c,c.begin()).
set_union(a.begin(), a.end(), b.begin(), b.end(), std::inserter(c,c.begin()));
An OutputIterator is one that can have its pointed-to values assigned to, and a std::insert_iterator<std::set<int>> achieves this by returning a proxy object, that inserts into the set when assigned to, rather than a int&
Alternatively, if you know how many items will result (or are prepared to overallocate), you could use a different container's begin, such as std::array<int, 7>, or a std::vector<int> whose size was sufficient to contain the 7 elements resulting from the union.
std::vector<int> d(a.size() + b.size(), 0); // preallocate enough
std::vector<int>::iterator end = std::set_union(a.begin(), a.end(), b.begin(), b.end(), d.begin());
d.erase(end, d.end()); // clean up any excess elements not from `a` or `b`

Checking for Item in std::vector<std::vector<std::string>> Using std::find

I have the following object
std::vector<std::vector<std::string>> vectorList;
Then I add to this using
std::vector<std::string> vec_tmp;
vec_tmp.push_back(strDRG);
vec_tmp.push_back(strLab);
if (std::find(vectorList.begin(), vectorList.end(), vec_tmp) == vectorList.end())
vectorList.push_back(vec_tmp);
The std::vector<std::string>s contained vectorList are only ever 2-dimensional and there are no duplicates. This works great, but I now only want to check if vectorList contains an item that index zero equal to the current strDrg. In C# I would not even be thinking about this, but this does not seem straight forward using C++. How can I find if a vector exists in vectorList where strDrg already exists in vectorList.at(i)[0]?
Note: I can use boost.
Use find_if with a lambda:
std::find_if(vectorList.begin(), vectorList.end(),
[&strDrg](const std::vector<std::string>& v) { return v[0] == strDrg; });
It seems you don't need the full power of vector for you inner elements. Consider using:
std::vector<std::array<std::string, 2>>
instead.
For doing exactly what you asked, std::find_if with a lambda as #chris proposed in comments is the best:
std::find_if(ob.begin(), ob.end(),
[&](const auto x){return x[0] == strDRG;});
// Replace auto with "decltype(ob[0])&" until
//you have a C++1y compiler. Might need some years.
But if you only ever have exactly two elements, consider using a std::array<...>, a std::pair<...> or a std::tuple<...> instead of the inner vector.
For tuple and pair, you need to access the first element differently:
pair : member first
tuple: use get<0>(x);

std::transform needs special care with sets

I don't understand why this snippet of code compiles:
#include <set>
#include <list>
#include <algorithm>
int modify(int i)
{
return 2*i;
}
int main (int args, char** argv)
{
std::set<int> a;
a.insert(1);
a.insert(2);
a.insert(3);
std::list<int> b; // change to set here
std::transform(a.begin(), a.end(), b.begin(), modify); // line 19
}
while, if I just change the type of b from std::list<int> to std::set<int> it fails at compilation time (at line 19) with the error: read-only variable is not assignable.
To use b as a set I need to change the transform line to
std::transform(a.begin(), a.end(), std::inserter(b, b.begin()), modify);
Why is that? I somehow guess the reason has to do with the fact that set is an associative container, while list is a sequence container, but I might be completely off the point here.
Edit
I forgot to mention: I tried this on gcc 3.4.2 and llvm 3.3 using the default standard (c++98). I tried again on llvm 3.3 using c++03 and I get the same behavior.
In your code without std::inserter, transform assigns to *b.begin(). In the case of set that's a const reference to an element (since C++11). Hence, a compile-time error.
In the case of the list it still assigns to *b.begin(), which compiles but has undefined behavior because the list has size 0. So b.begin() may not be dereferenced.
You are correct that this is to do with the fact that set is an associative container whereas list is a sequence. Associative containers don't let you modify the part of the element used as a key. In the case of set that part is the element, for map you can modify the value but not the key.
The whole point of std::inserter is to arrange that instead of assigning through an iterator, it calls insert.
First, the code as you have it exhibits undefined behavior, since the target list doesn't actually have space. Use a back_inserter to create space as you go.
As for the set, a set's elements are immutable. This is why you can't assign to a dereferenced iterator, even if you had space. But using the inserter is perfectly fine.
In C++03 this code compiles but results in undefined behavior - you cannot simply change values of set because they must be in ascending order. In c++11 that was fixed and set::iterator is a bidirectional iterator on const T, so you cannot change its values at all. std::inserter does not change existing values, instead it inserts new values in operator++ and so everything works
This record
std::transform(a.begin(), a.end(), b.begin(), modify);
is invalid even for std::list<int> (though it can be compiled for std::list or std::set in C++ 2003 where set has non-const iterator) because you defined an empty list.
std::list<int> b;
When such a record is used it is supposed that the output container already has elements in the range
[b.begin(), b.begin() + distance( a.begin(), a.end() ) )
because they are reassigned.
So if to consider a set then it is supposed that the set has already all required elements but you may not change them. When you use iterator adapter std::insert_iterator then it adds new elements in the container. So in this case you may use a set.

Efficiently delete double entries in C++ STL vector? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
C++ Delete Duplicate Entries in a vector
I need to delete double entries in C++ STL vectors. An important point is that the order of elements in the resulting vector must be equivalent to the order in the input vector. Is there an algorithm (e.g. in stl, boost) which would do this?
There are two possible cases here: either the vector is already sorted or it isn't.
If it is, std::erase and std::unique can easily solve this as shown in the other answers.
If it isn't then you can do achieve the goal with
v.erase(std::remove_if(v.begin(), v.end(), predicate), v.end());
but there's a problem in that predicate is not trivial to specify: it's a function that accepts one argument (the value to consider) and it needs to answer the question "is there any equal value earlier in the vector?". Since you aren't told where exactly in the vector the supplied argument is, that means you 'd have to keep quite a bit of manual state to be able to answer this.
A convenient option here would be to use an std::set to do some of the heavy lifting:
std::set<decltype(v)::value_type> set(v.begin(), v.end());
v.erase(
std::remove_if(
v.begin(),
v.end(),
[&set] (decltype(v)::value_type item) { return !set.erase(item); }),
v.end());
What this does is prepopulate an std::set with the values in the vector and then check if an item has been seen before by seeing if it has been removed from the set. This way the result will retain only the first item from each set of items that compare equal in the input.
See it in action.
If your vector is not sorted and you thus cannot just use std::unique (and likewise cannot sort it which would destroy your order), you can use something like this function (using C++11 lambdas):
template<typename FwdIt> FwdIt unordered_unique(FwdIt first, FwdIt last)
{
typedef typename std::iterator_traits<FwdIt>::value_type value_type;
std::set<value_type> unique;
return std::remove_if(first, last, [&unique](const value_type &arg) {
return !unique.insert(arg).second; });
}
Which can be invoke using the usual erase-romve-idiom:
v.erase(unordered_unique(v.begin(), v.end()), v.end());
Of course you can also use C++11's std::unordered_set instead of a std::set (for hashable types, of course) to get away from O(n log n) in average case.
How about std::unique?
auto firstDup = std::unique(myvector.begin(), myvector.end());
Use next:
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );