Sort items but some fixed

Function

template <typename Container, typename Comparator, typename Predicate> void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) 

consists in sorting the container c according to the comp ordering criterion, but those elements that satisfy pred remain stationary in their original positions after sorting (i.e., they are not subject to sorting).

I tried to adapt the quick view to fit this, but could not think of it. In the end, I decided to adapt coarse sorting to complete the task:

 #include <iostream> #include <vector> std::vector<int> numbers = {5,7,1,8,9,3,20,2,11}; template <typename Container, typename Comparator, typename Predicate> void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) { // O(n^2), but want O(nlogn) on average (like quick sort or merge sort) const std::size_t N = c.size(); std::size_t i, j, minIndex; for (i = 0; i < N-1; i++) { if (pred(c[i])) continue; // c[i] shall not swap with any element. minIndex = i; for (j = i + 1; j < N; j++) { if (pred(c[j])) continue; // c[j] shall not swap with any element. if (comp(c[j], c[minIndex])) minIndex = j; } if (minIndex != i) std::swap(c[i], c[minIndex]); } } int main() { sortButKeepSomeFixed (numbers, std::greater<int>(), // Ordering condition. [](int x) {return x % 2 == 0;}); // Those that shall remain fixed. for (int x : numbers) std::cout << x << ' '; // 11 9 7 8 5 3 20 2 1 } 

But the time complexity is O (N ^ 2) (I think). Could someone improve the time complexity here, perhaps O (NlogN) on average? In other words, find a general best algorithm using recursion or something like that?

Or maybe the best idea is to take out the elements that satisfy pred , sort what remains with std::sort , and then return the extracted elements to their original position? Will it be more effective, or will it only make it worse?

Update: This is based on Beta's assumption (sorting iterators that fail pred ). But although the elements that pass pred do remain fixed, the sorting at the end is incorrect.

 template <typename Container, typename Comparator, typename Predicate> void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) { std::vector<typename Container::iterator> iterators; for (typename Container::iterator it = c.begin(); it != c.end(); ++it) { if (!pred(*it)) iterators.emplace_back(it); } std::vector<typename Container::iterator> originalIterators = iterators; std::sort(iterators.begin(), iterators.end(), [comp](const typename Container::iterator& x, const typename Container::iterator& y) {return comp(*x, *y);}); for (int i = 0; i < originalIterators.size(); i++) *originalIterators[i] = *iterators[i]; } 

Wrong conclusion 11 9 9 8 11 3 20 2 9 if it should be 11 9 7 8 5 3 20 2 1 .

+7
c ++ sorting lambda templates
source share
2 answers

It's fun. At first, I tried to code the correct IMO approach using a custom iterator that simply skips elements that satisfy the predicate. It turned out to be quite complicated, at least writing on a mobile phone when I do this.

Basically, this should result in code similar to what you can find in the Eric Niebler v3 ranges .

But there is a simpler, more direct approach that you are trying to use above. The problem with your non-working solution is that it changes the values ​​pointed to by the (remaining sorted) iterators when assigned in the last for loop. This problem can be fixed by copying, as in my code:

 int main(int, char **) { vector<int> input {1,2,3,4,5,6,7,8,9}; vector<reference_wrapper<int>> filtered{begin(input), end(input)}; filtered.erase(remove_if(begin(filtered), end(filtered), [](auto e) {return e%2==0;}), end(filtered)); vector<int> sorted{begin(filtered), end(filtered)}; // change that to contain reference wrappers to see the issue sort(begin(sorted), end(sorted), greater<int>{}); transform(begin(filtered), end(filtered), begin(sorted), begin(filtered), [](auto to, auto from) { to.get() = from; return to;}); copy(begin(input), end(input), ostream_iterator<int>{cout, ", "}); return 0; } 

Live example here. Forgot in fork before modification, sorry.

(Instead of using copies for types that use data allocated by the move heap, you should probably use it. Although I'm not sure if it can be assigned to a moved object.)

Using ... a rather strange ... wrapper std::reference_wrapper instead of std::reference_wrapper makes it possible to filter without sorting , to use a vector with (copied or moved) elements of type value:

 template <class T> class copyable_ref { public: copyable_ref(T& ref) noexcept : _ptr(std::addressof(ref)), _copied(false) {} copyable_ref(T&&) = delete; copyable_ref(const copyable_ref& x) noexcept : _ptr (new int(*x._ptr)), _copied (true) { } ~copyable_ref() { if (_copied) { delete _ptr; } } copyable_ref& operator=(const copyable_ref& x) noexcept { *_ptr = *x._ptr; } operator T& () const noexcept { return *_ptr; } T& get() const noexcept { return *_ptr; } private: T* _ptr; bool _copied; }; 

During construction, this class stores a pointer to its argument, which also changes when the copy destination operator is used. But when an instance is created by the copy, then a bunch of the selected copy of the reference (other) value is created. Thus, you can replace the two reference values ​​with code similar to

 Value a, b; copyable_ref<Value> ref_a{a}, ref_b{b}; copyable_ref<Value> temp{ref_a}; ref_a = ref_b; ref_b = temp; // a and b are swapped 

This was necessary because std::sort does not seem to use swap (found through ADL or std::swap ), but code equivalent to the one above.

Now you can sort the filtered “view” by filling the vector with instances ( not copied by the designed ones ) of the strange wrapper class and sorting this vector. As the result in the example shows, no more than one heap is allocated a copy of the value type. Apart from the required size for pointers inside the shell, this class allows filtering sorting with a constant spatial cost:

  vector<int> input {1,2,3,4,5,6,7,8,9}; vector<copyable_ref<int>> sorted; sorted.reserve(input.size()); for (auto & e : input) { if (e % 2 != 0) { sorted.emplace_back(e); } } sort(begin(sorted), end(sorted), greater<int>{}); copy(begin(input), end(input), ostream_iterator<int>{cout, ", "}); cout << endl; // 9 2 7 4 5 6 3 8 1 

Finally, although this works pretty well, I probably won’t use this in production code. I was especially surprised that std::sort did not use my own swap implementation, which led to this adventurous copy constructor.


You cannot generalize your code to work with sets and maps: they are sorted by design, and this requires that the fixed order function properly. And unordered options, well, unordered and therefore cannot maintain order. But you can always (unless you modify the container) use std::reference_wrapper inside the vector to provide a sorted “view” of your data.

+1
source share

Based on the idea of ​​beta for sorting using iterators, although I'm not sure what time complexity is. It also does not work with all containers, for example. std :: set, std :: map.

 template <typename Container, typename Comparator, typename Predicate> void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred) { std::vector<typename Container::value_type> toSort; std::vector<typename Container::iterator> iterators; for (typename Container::iterator it = c.begin(); it != c.end(); ++it) { if (!pred(*it)) { toSort.emplace_back(*it); iterators.emplace_back(it); } } std::sort(toSort.begin(), toSort.end(), comp); for (std::size_t i = 0; i < toSort.size(); i++) *iterators[i] = toSort[i]; } std::vector<int> vector = {5,7,1,8,9,3,20,2,11}; std::array<int, 9> array = {5,7,1,8,9,3,20,2,11}; std::list<int> list = {5,7,1,8,9,3,20,2,11}; std::set<int> set = {5,7,1,8,9,3,20,2,11}; std::map<double, int> map = { {1.5,5}, {1.2,7}, {3.5,1}, {0.5,8}, {5.2,9}, {7.5,3}, {0.1,20}, {1.8,2}, {2.4,11} }; template <typename Container> void test (Container& container) { sortButKeepSomeFixed (container, std::greater<int>(), // Ordering condition. [](int x) {return x % 2 == 0;}); // Those that shall remain fixed. for (int x : container) std::cout << x << ' '; std::cout << '\n'; } int main() { test(vector); // 11 9 7 8 5 3 20 2 1 test(array); // 11 9 7 8 5 3 20 2 1 test(list); // 11 9 7 8 5 3 20 2 1 test(set); // Does not compile. sortButKeepSomeFixed (map, [](const std::pair<double, int>& x, const std::pair<double, int>& y) {return x.second > y.second;}, [](const std::pair<double, int>& x) {return x.second % 2 == 0;}); for (const std::pair<double, int>& x : map) std::cout << "(" << x.first << "," << x.second << ") "; // Does not compile. } 

The error for dialing and map is "Read-only location assignment". Does anyone know how to generalize this to work with sets and maps?

Update. Therefore, I suggest a set, maps, etc., just delete those elements that satisfy pred , and create a new set / map / ... using Compare as the type key_compare. As shown below. But this is only for the multitude. How to generalize it to other containers having key_compare types?

 template <typename Container, typename Comparator, typename Predicate> std::set<typename Container::value_type, Comparator, typename Container::allocator_type> sortButRemoveSomeElements (Container& c, const Comparator&, const Predicate& pred) { std::set<typename Container::value_type, Comparator, typename Container::allocator_type> set; std::vector<typename Container::value_type> keep; for (typename Container::iterator it = c.begin(); it != c.end(); ++it) { if (!pred(*it)) keep.emplace_back(*it); } for (typename Container::value_type x : keep) set.emplace(x); // Sorted by Comparator automatically due to std::set insertion property. return set; } 

Test:

 struct GreaterThan { bool operator()(int x, int y) const {return x > y;} }; std::set<int, GreaterThan> newSet = sortButRemoveSomeElements (set, GreaterThan{}, // Ordering condition. [](int x) {return x % 2 == 0;}); // Those that shall be removed. for (int x : newSet) std::cout << x << ' '; // 11 9 7 5 3 1 
0
source share

All Articles