实现 partition_unique 和 stable_partition_unique 算法
Posted
技术标签:
【中文标题】实现 partition_unique 和 stable_partition_unique 算法【英文标题】:Implementing partition_unique and stable_partition_unique algorithms 【发布时间】:2016-04-27 11:08:43 【问题描述】:我正在寻找一种方法来划分一组有序元素,以便所有唯一元素出现在它们各自的重复元素之前,注意到std::unique
不适用,因为重复元素被覆盖,我想到了使用std::partition
。调用这个算法partition_unique
,我也需要对应的stable_partition_unique
(即喜欢stable_partition
)。
partition_unique
的基本实现是:
#include <algorithm>
#include <iterator>
#include <unordered_set>
#include <functional>
template <typename BidirIt, typename BinaryPredicate = std::equal_to<void>>
BidirIt partition_unique(BidirIt first, BidirIt last, BinaryPredicate p = BinaryPredicate )
using ValueTp = typename std::iterator_traits<BidirIt>::value_type;
std::unordered_set<ValueTp, std::hash<ValueTp>, BinaryPredicate> seen ;
seen.reserve(std::distance(first, last));
return std::partition(first, last,
[&p, &seen] (const ValueTp& value)
return seen.insert(value).second;
);
可以这样使用:
#include <vector>
#include <iostream>
int main()
std::vector<int> vals 1, 1, 2, 4, 5, 5, 5, 7, 7, 9, 10;
const auto it = partition_unique(std::begin(vals), std::end(vals));
std::cout << "Unique values: ";
std::copy(std::begin(vals), it, std::ostream_iterator<int> std::cout, " "); // Unique values: 1 10 2 4 5 9 7
std::cout << '\n' << "Duplicate values: ";
std::copy(it, std::end(vals), std::ostream_iterator<int> std::cout, " "); // Duplicate values: 7 5 5 1
对应的stable_partition_unqiue
可以通过将std::partition
替换为std::stable_partition
来实现。
这些方法的问题在于,它们不必要地缓冲了 std::unordered_set
中的所有唯一值(这还增加了哈希函数要求),在对元素进行排序时,这不应该是必需的。为partition_unique
提出一个更好的实现并不需要太多工作,但stable_partition_unique
的实现似乎要困难得多,如果可能的话,我宁愿不要implement this myself。
有没有办法使用现有的算法来实现最优的partition_unique
和stable_ partition_unique
算法?
【问题讨论】:
虽然它可能适用于std::partition()
的大多数实现,但我会假设谓词不允许 在不同时间为同一个参数返回不同的值。您的谓词确实意味着它可能导致例如std::partition()
的并行实现会崩溃。
@j_random_hacker std::partition
由于复杂性要求,只允许检查每个值一次,所以这应该不是问题。
这是一个有趣的观察。 AFAICT 这实际上允许std::partition()
的正确并行实现,即使面对“非常量”谓词(调用者有责任提供任何必要的互斥)。
为什么不使用 stable_sort()?任何重复项都将在第一个元素之后立即保留,并保留顺序。最好的算法不依赖于额外的容器。 stable_partition() 也保留顺序,但重复项不一定相邻。
【参考方案1】:
创建一个队列来保存重复项。然后,初始化两个索引,src
和dest
,从索引 1 开始,遍历列表。如果当前项 (list[src]
) 等于前一项 (list[dest-1]
),则将其复制到队列中。否则,将其复制到list[dest]
并递增dest
。
当您用完列表时,将队列中的项目复制到原始列表的尾部。
类似:
Queue dupQueue
int src = 1
int dest = 1
while (src < list.count)
if (list[src] == list[dest-1])
// it's a duplicate.
dupQueue.push(list[src])
else
list[dest] = list[src]
++dest
++src
while (!dupQueue.IsEmpty)
list[dest] = dupQueue.pop()
++dest
我知道 STL 有一个队列。有没有类似上面的算法,我不知道。
【讨论】:
OP 不想实现该算法。他想结合现有的算法。标准stable_partition
的实现方式与您在 GCC 的 STL 中描述的方式几乎相同。因此,使用您的答案将是重新实现。不幸的是,我越想这个问题,我就越觉得你必须重新实现......
@fjardon:我意识到 OP 宁愿不自己实现它。不过,正如你所说,看来他必须这样做。而且,与他在问题中的断言相反,这并不比stable_partition
“困难得多”:只需用队列替换无序集,就可以了。
@JimMischel 实现一个简单的实现并不难,但是一个好的std::stable_parition
(因此stable_partition_unique
)要复杂得多——它必须根据可用内存实现较慢的回退,并且可以针对不同的迭代器类型进行优化。看看implementation in libc++,我想你会同意的。以上是关于实现 partition_unique 和 stable_partition_unique 算法的主要内容,如果未能解决你的问题,请参考以下文章