对元素进行排序,但保留某些固定的元素
Sort elements, but keep certain ones fixed
功能
template <typename Container, typename Comparator, typename Predicate>
void sortButKeepSomeFixed (Container& c, const Comparator& comp, const Predicate& pred)
是按照排序标准comp
对容器c
进行排序,但满足pred
的元素在排序后保持固定在原来的位置(即不受排序影响)。
我试图调整快速排序来适应这个,但不能想到它。最后,我决定采用粗选择排序来完成工作:
#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
}
但是时间复杂度是O(N^2)(我认为)。有人能把时间复杂度提高到平均0 (NlogN)吗?换句话说,找到一个整体更好的算法,使用递归或类似的东西?
或者一个更好的主意是取出满足pred
的元素,对std::sort
剩下的元素进行排序,然后将提取的元素放回到原来的位置?这样会更有效率吗,还是会让事情变得更糟?
更新:这是基于Beta的建议(对没有通过pred
的迭代器进行排序)。但是,虽然传递pred
的元素确实保持固定,但最后的排序是不正确的。
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];
}
正确输出为11 9 7 8 5 3 20 2 1
,错误输出为11 9 9 8 11 3 20 2 9
这个很有趣。我首先尝试编写IMO正确的方法,使用一个自定义迭代器,该迭代器只跳过满足谓词的元素。这被证明是相当具有挑战性的,至少在我这样做的时候在手机上写这些。
基本上,这应该导致类似于您在Eric Niebler的ranges v3中找到的代码。
但是还有一种更简单、更直接的方法,就是你上面尝试使用的。你的非工作解决方案的问题是,它改变了(剩下的排序)迭代器在最后一个for
循环中赋值时指向的值。这个问题可以通过复制来避免,就像我的代码:
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;
}
这里有一个实例。修改前忘记分叉了,抱歉
(对于使用堆分配数据的类型,可能应该使用move
,而不是最后使用副本。虽然我不确定你是否可以分配一个移动的对象)
使用…相当奇怪……包装器类而不是std::reference_wrapper
使实现过滤排序成为可能,而不必使用值类型为
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;
};
在构造时,该类存储一个指向其实参的指针,当使用复制赋值操作符时,该指针也会被修改。但是,当一个实例被复制构造时,就会生成(由另一个)引用值的堆分配副本。这样,就可以用类似于
的代码交换两个引用值。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
这是必要的,因为std::sort
似乎没有使用swap
(通过ADL或std::swap
找到),而是与上面的代码等效。
现在可以通过使用(而不是复制构造的)奇怪的包装器类的实例填充向量并对该向量排序来对过滤后的"视图"进行排序。正如示例中的输出所示,最多有一个值类型的堆分配副本。不计算包装器内部指针所需的大小,该类支持具有恒定空间开销的过滤排序:
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
最后,虽然这工作得很好,但我可能不会在生产代码中使用它。我特别惊讶的是,std::sort
没有使用我自己的swap
实现,这导致了这个冒险的复制构造函数。
你不能泛化你的代码来处理集合和映射:它们是按设计排序的,它们需要固定的顺序才能正常工作。无序变异体是无序的,因此不能保持有序。但是你总是可以(只要你不修改容器)使用 std::reference_wrapper
s内部的矢量来提供一个排序的"视图"的数据。
基于Beta使用迭代器排序的想法,尽管我不确定时间复杂度是多少。它也不适用于所有的容器,例如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.
}
set和map的错误是"assign of read-only location"。有人知道如何将其推广到集合和映射中吗?
更新:所以我建议设置,地图等…,只需删除那些满足pred
的元素,然后创建一个新的set/map/…使用Compare
作为它们的key_compare类型。像下面。但它只适用于set。如何将其推广到其他具有key_compare类型的容器?
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's insertion property.
return set;
}
测试: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
- 即使在使用 delete[] 后仍保留的元素
- 调用QVector::保留即使不知道确切的元素数量?
- 在数组的开头添加一个元素并移动其余元素,但保留大小
- 保留从一个多集复制到另一个多集的元素的顺序
- 引用保留向量中的第一个元素
- 使用 STL 仅保留 N 个最小元素(带有重复项)
- 单独保留重复元素
- 仅保留给定列表中不重复的元素.例如:(a b a a a c c) 给我们 (a b)
- 自定义容器在保留空间时不必要地创建新元素实例
- 在向量 (C++) 中保留插入元素的地址
- 将元素保留在动态数组中
- 矢量元素如何在矢量 std::move 之后保留其原始地址
- 保留 std::vector 的前 N 个元素<>并删除其余元素
- 组合两个矩阵,保留其元素值
- 分配给尚未调整大小但保留的向量元素,称为合法
- 如何在不初始化每个元素的情况下为数组保留空间
- 将最小的n个元素保留在集合中,丢弃其他元素
- 对容器进行排序,然后在保留原始排序的情况下移动元素
- unordered_set::erase(pos)是否保留元素的顺序?
- 我如何构建公共数据,同时保留默认的移动元素和分配