实现 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_uniquestable_ partition_unique 算法?

【问题讨论】:

虽然它可能适用于std::partition() 的大多数实现,但我会假设谓词不允许 在不同时间为同一个参数返回不同的值。您的谓词确实意味着它可能导致例如std::partition() 的并行实现会崩溃。 @j_random_hacker std::partition 由于复杂性要求,只允许检查每个值一次,所以这应该不是问题。 这是一个有趣的观察。 AFAICT 这实际上允许std::partition() 的正确并行实现,即使面对“非常量”谓词(调用者有责任提供任何必要的互斥)。 为什么不使用 stable_sort()?任何重复项都将在第一个元素之后立即保留,并保留顺序。最好的算法不依赖于额外的容器。 stable_partition() 也保留顺序,但重复项不一定相邻。 【参考方案1】:

创建一个队列来保存重复项。然后,初始化两个索引,srcdest,从索引 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 算法的主要内容,如果未能解决你的问题,请参考以下文章

STA(01)

STA(01)

STA(01)

STA(01)

哪些阻塞操作会导致 STA 线程泵送 COM 消息?

2.13 正点原子ESP8266模块的STA模式 调试2