C++ 从向量中删除最小的非唯一值

C++ 从向量中删除最小的非唯一值,c++,algorithm,c++11,unique,C++,Algorithm,C++11,Unique,我有一个未排序的double向量(实际上是具有double成员的对象,在本例中使用)。从这个向量中,我需要删除最小的非唯一值。但是,不能保证存在非唯一值。允许对范围进行排序 像往常一样,我从寻找std::算法开始,发现std::unique。在我的第一个想法中,我将结合使用std::sort将所有非唯一值移动到向量的末尾,然后在非唯一值上使用min_元素。但是,std::unique会将非唯一值保留在未指定的状态。事实上,我失去了所有非POD成员 有没有人建议如何有效地做到这一点?有效地进行这项

我有一个未排序的double向量(实际上是具有double成员的对象,在本例中使用)。从这个向量中,我需要删除最小的非唯一值。但是,不能保证存在非唯一值。允许对范围进行排序

像往常一样,我从寻找std::算法开始,发现std::unique。在我的第一个想法中,我将结合使用std::sort将所有非唯一值移动到向量的末尾,然后在非唯一值上使用min_元素。但是,std::unique会将非唯一值保留在未指定的状态。事实上,我失去了所有非POD成员


有没有人建议如何有效地做到这一点?有效地进行这项工作很重要,因为代码是在程序的瓶颈中使用的(这已经有点太慢了)。

如果您可以对范围进行排序,那么这就很容易了。按升序排序,然后迭代,直到遇到两个等价的相邻元素。完成了

大概是这样的:

T findSmallestNonunique(std::vector<T> v)
{
   std::sort(std::begin(v), std::end(v));
   auto it = std::adjacent_find(std::begin(v), std::end(v));
   if (it == std::end(v))
      throw std::runtime_error("No such element found");
   return *it;
}
T findsmalestunnique(标准::向量v)
{
std::sort(std::begin(v),std::end(v));
自动it=std::相邻查找(std::开始(v),std::结束(v));
如果(it==std::end(v))
抛出std::runtime_错误(“未找到此类元素”);
归还它;
}
这是:

#包括
#包括
#包括
#包括
模板
typename容器::值\类型FindSalestUnUnique(容器c)
{
std::sort(std::begin(c),std::end(c));
自动it=std::相邻的查找(std::开始(c),std::结束(c));
如果(it==std::end(c))
抛出std::runtime_错误(“未找到此类元素”);
归还它;
}
int main(int argc,字符**argv)
{
std::向量v;
对于(int i=1;i
  • 使用
    unordered_map
    对元素进行计数。这在值的数量上是(预期的)线性的

  • 使用朴素循环在非唯一项中查找最小项

下面是一个可能的实现:

#include <unordered_map>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    const vector<double> elems{1, 3.2, 3.2, 2};
    unordered_map<double, size_t> c;
    for(const double &d: elems)
        ++c[d];
    bool has = false;
    double min_;
    for(const auto &e: c)
        if(e.second > 1)
        {
            min_ = has? min(e.first, min_): e.first;
            has = true;
        }
    cout << boolalpha << has << " " << min_ << endl;
    return 0;
}
#包括
#包括
#包括
使用名称空间std;
int main()
{
常数向量元素{1,3.2,3.2,2};
无序地图c;
用于(常数双d:elems)
++c[d];
bool has=false;
双敏;
用于(常数自动和电气:c)
如果(例如秒>1)
{
min_uuz=has?min(e.first,min_uuuz):e.first;
has=真;
}

cout好吧,这里有一个算法,它实际上删除了最小的非唯一项(而不是只打印它)

以下是我的结果:

Array size = 5  range = 7
Method1 : 8.61967e-06s
Method2 : 1.49667e-07s
Method3 : 2.69e-07s
Method4 : 2.47667e-07s

Array size = 50  range = 75
Method1 : 2.0749e-05s
Method2 : 1.404e-06s
Method3 : 9.23e-07s
Method4 : 8.37e-07s

Array size = 500  range = 750
Method1 : 0.000163868s
Method2 : 1.6899e-05s
Method3 : 4.39767e-06s
Method4 : 3.78733e-06s

Array size = 5000  range = 7500
Method1 : 0.00124788s
Method2 : 0.000258637s
Method3 : 3.32683e-05s
Method4 : 4.70797e-05s

Array size = 50000  range = 75000
Method1 : 0.0131954s
Method2 : 0.00344415s
Method3 : 0.000346838s
Method4 : 0.000183092s

Array size = 500000  range = 750000
Method1 : 0.25375s
Method2 : 0.0400779s
Method3 : 0.00331022s
Method4 : 0.00343761s

Array size = 5000000  range = 7500000
Method1 : 3.82532s
Method2 : 0.466848s
Method3 : 0.0426554s
Method4 : 0.0278986s
更新

我已经用更新了上面的结果。他的算法很有竞争力。干得好,乌尔里奇

我应该提醒读者,Ulrich的算法容易受到“快速排序O(N^2)问题”的影响,其中对于特定输入,算法可能会严重退化。通用算法是可修复的,Ulrich显然意识到了该漏洞,这一点可以从以下评论中得到证明:

// pick pivot (TODO: randomize pivot?)
这是针对O(N^2)问题的一种防御,还有其他防御,例如检测不合理的递归/迭代,并在中途切换到另一种算法(如方法3或方法2)。如书面所述,方法4在按顺序排列时受到严重影响,在按相反顺序排列时则是灾难性的。在我的平台上,对于这些情况,方法3也次优于方法2,尽管不如方法4差


找到用于快速排序算法(如算法)的O(n 2)问题的理想技术有点黑艺术,但值得花时间。我一定会把方法4看作工具箱中的一个有价值的工具。

首先,关于移除元素的任务,最难的是找到它,但是实际上移除它是容易的。(与最后一个元素交换,然后
pop_back()
)。因此,我将只讨论这个发现。此外,您提到排序序列是可以接受的,但我从中得出,不仅排序,而且任何类型的重新排序都是可以接受的

请看一下快速排序算法。它选择一个随机元素,然后将序列向左和向右划分。如果您编写分区,以便不仅区分“更少”和“不更少”,还可以对序列进行部分排序,并在运行中找到最小的重复项

应执行以下步骤:

  • 首先,选择一个随机数据透视并对序列进行分区。同时,您可以检测数据透视是否重复。请注意,如果您在此处发现重复数据透视,您可以丢弃(!)任何更大的数据透视,因此您甚至不必为两个分区投资存储容量和带宽
  • 然后,递归到较小元素的序列
  • 如果在较小的分区中有一组重复项,那么这些就是您的解决方案
  • 如果第一个轴有重复项,这就是您的解决方案
  • 否则,递归到较大的元素以查找重复项
与常规排序相比的优点是,如果在较低的数字中发现重复项,则不会对整个序列进行排序。但是,对于唯一的数字序列,您将对其进行完全排序。与使用哈希映射的建议元素计数相比,这确实具有更高的渐近复杂性。它是否执行得更好取决于您的i实现和输入数据

请注意,这要求可以对元素进行排序和比较。您提到您使用了
双值
,这是出了名的不好排序,当您有NaN时。我可以想象,标准容器中的哈希算法可以与NaN一起工作,因此使用哈希映射进行计数还有一点

下面的代码实现了上述算法。它使用一个递归函数对输入进行分区,并从第二个
#include <unordered_map>
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
#include <cassert>

template <typename Container>
void
erase_using_hashTable(Container& vec)
{
    using T = typename Container::value_type;
    std::unordered_map<T, int> c;
    for (const auto& elem : vec){
        ++c[elem];
    }
    bool has = false;
    T min_;
    for (const auto& e : c)
    {
        if (e.second > 1)
        {
            min_ = has ? std::min(e.first, min_) : e.first;
            has = true;
        }
    }
    if (has)
        vec.erase(std::find(vec.begin(), vec.end(), min_));
}

template <typename Container>
void 
eraseSmallestNonunique(Container& c)
{
   std::sort(std::begin(c), std::end(c));
   auto it = std::adjacent_find(std::begin(c), std::end(c));

   if (it != std::end(c))
       c.erase(it);
}

template <typename Container>
void
removeSmallestNonunique(Container& c)
{
    using value_type = typename Container::value_type;
    if (c.size() > 1)
    {
        std::make_heap(c.begin(), c.end(), std::greater<value_type>{});
        std::pop_heap(c.begin(), c.end(), std::greater<value_type>{});
        for (auto e = std::prev(c.end()); e != c.begin(); --e)
        {
            std::pop_heap(c.begin(), e, std::greater<value_type>{});
            if (*e == e[-1])
            {
                c.erase(e);
                break;
            }
        }
    }
}

template<typename iterator>
iterator partition_and_find_smallest_duplicate(iterator begin, iterator end)
{
    using std::swap;
    if (begin == end)
        return end; // empty sequence

    // The range begin,end is split in four partitions:
    // 1. equal to the pivot
    // 2. smaller than the pivot
    // 3. unclassified
    // 4. greater than the pivot

    // pick pivot (TODO: randomize pivot?)
    iterator pivot = begin;
    iterator first = next(begin);
    iterator last = end;

    while (first != last) {
        if (*first > *pivot) {
            --last;
            swap(*first, *last);
        } else if (*first < *pivot) {
            ++first;
        } else {
            ++pivot;
            swap(*pivot, *first);
            ++first;
        }
    }

    // look for duplicates in the elements smaller than the pivot
    auto res = partition_and_find_smallest_duplicate(next(pivot), first);
    if (res != first)
        return res;

    // if we have more than just one equal to the pivot, it is the smallest duplicate
    if (pivot != begin)
        return pivot;

    // neither, look for duplicates in the elements greater than the pivot
    return partition_and_find_smallest_duplicate(last, end);
}

template<typename container>
void remove_smallest_duplicate(container& c)
{
    using std::swap;
    auto it = partition_and_find_smallest_duplicate(c.begin(), c.end());
    if (it != c.end())
    {
        swap(*it, c.back());
        c.pop_back();
    }
}

int  main()
{
    const int MaxArraySize = 5000000;
    const int minArraySize = 5;
    const int numberOfTests = 3;

    //std::ofstream file;
    //file.open("test.txt");
    std::mt19937 generator;

    for (int t = minArraySize; t <= MaxArraySize; t *= 10)
    {
        const int range = 3*t/2;
        std::uniform_int_distribution<int> distribution(0,range);

        std::cout << "Array size = " << t << "  range = " << range << '\n';

        std::chrono::duration<double> avg{},avg2{}, avg3{}, avg4{};
        for (int n = 0; n < numberOfTests; n++)
        {
            std::vector<int> save_vec;
            save_vec.reserve(t);
            for (int i = 0; i < t; i++){//por kardan array ba anasor random
                save_vec.push_back(distribution(generator));
            }
            //method1
            auto vec = save_vec;
            auto start = std::chrono::steady_clock::now();
            erase_using_hashTable(vec);
            auto end = std::chrono::steady_clock::now();
            avg += end - start;
            auto answer1 = vec;
            std::sort(answer1.begin(), answer1.end());

            //method2
            vec = save_vec;
            start = std::chrono::steady_clock::now();
            eraseSmallestNonunique(vec);
            end = std::chrono::steady_clock::now();
            avg2 += end - start;
            auto answer2 = vec;
            std::sort(answer2.begin(), answer2.end());
            assert(answer2 == answer1);

            //method3
            vec = save_vec;
            start = std::chrono::steady_clock::now();
            removeSmallestNonunique(vec);
            end = std::chrono::steady_clock::now();
            avg3 += end - start;
            auto answer3 = vec;
            std::sort(answer3.begin(), answer3.end());
            assert(answer3 == answer2);

            //method4
            vec = save_vec;
            start = std::chrono::steady_clock::now();
            remove_smallest_duplicate(vec);
            end = std::chrono::steady_clock::now();
            avg4 += end - start;
            auto answer4 = vec;
            std::sort(answer4.begin(), answer4.end());
            assert(answer4 == answer3);
        }
        //file << avg/numberOfTests <<" "<<avg2/numberOfTests<<'\n';
        //file << "__\n";
        std::cout <<   "Method1 : " << (avg  / numberOfTests).count() << 's'
                  << "\nMethod2 : " << (avg2 / numberOfTests).count() << 's'
                  << "\nMethod3 : " << (avg3 / numberOfTests).count() << 's'
                  << "\nMethod4 : " << (avg4 / numberOfTests).count() << 's'
                  << "\n\n";
    }

}
Array size = 5  range = 7
Method1 : 8.61967e-06s
Method2 : 1.49667e-07s
Method3 : 2.69e-07s
Method4 : 2.47667e-07s

Array size = 50  range = 75
Method1 : 2.0749e-05s
Method2 : 1.404e-06s
Method3 : 9.23e-07s
Method4 : 8.37e-07s

Array size = 500  range = 750
Method1 : 0.000163868s
Method2 : 1.6899e-05s
Method3 : 4.39767e-06s
Method4 : 3.78733e-06s

Array size = 5000  range = 7500
Method1 : 0.00124788s
Method2 : 0.000258637s
Method3 : 3.32683e-05s
Method4 : 4.70797e-05s

Array size = 50000  range = 75000
Method1 : 0.0131954s
Method2 : 0.00344415s
Method3 : 0.000346838s
Method4 : 0.000183092s

Array size = 500000  range = 750000
Method1 : 0.25375s
Method2 : 0.0400779s
Method3 : 0.00331022s
Method4 : 0.00343761s

Array size = 5000000  range = 7500000
Method1 : 3.82532s
Method2 : 0.466848s
Method3 : 0.0426554s
Method4 : 0.0278986s
// pick pivot (TODO: randomize pivot?)
#include <vector>
#include <algorithm>
#include <iostream>

template<typename iterator>
iterator partition_and_find_smallest_duplicate(iterator begin, iterator end)
{
    using std::swap;

    std::cout << "find_duplicate(";
    for (iterator it=begin; it!=end; ++it)
        std::cout << *it << ", ";
    std::cout << ")\n";
    if (begin == end)
        return end; // empty sequence

    // The range begin,end is split in four partitions:
    // 1. equal to the pivot
    // 2. smaller than the pivot
    // 3. unclassified
    // 4. greater than the pivot

    // pick pivot (TODO: randomize pivot?)
    iterator pivot = begin;
    std::cout << "picking pivot: " << *pivot << '\n';

    iterator first = next(begin);
    iterator last = end;

    while (first != last) {
        if (*first > *pivot) {
            --last;
            swap(*first, *last);
        } else if (*first < *pivot) {
            ++first;
        } else {
            ++pivot;
            swap(*pivot, *first);
            ++first;
            std::cout << "found duplicate of pivot\n";
        }
    }

    // look for duplicates in the elements smaller than the pivot
    auto res = partition_and_find_smallest_duplicate(next(pivot), first);
    if (res != first)
        return res;

    // if we have more than just one equal to the pivot, it is the smallest duplicate
    if (pivot != begin)
        return pivot;

    // neither, look for duplicates in the elements greater than the pivot
    return partition_and_find_smallest_duplicate(last, end);
}

template<typename container>
void remove_smallest_duplicate(container& c)
{
    using std::swap;
    auto it = partition_and_find_smallest_duplicate(c.begin(), c.end());
    if (it != c.end())
    {
        std::cout << "removing duplicate: " << *it << std::endl;

        // swap with the last last element before popping
        // to avoid copying the elements in between
        swap(*it, c.back());
        c.pop_back();
    }
}

int main()
{
    std::vector<int> data = {66, 3, 11, 7, 75, 62, 62, 52, 9, 24, 58, 72, 37, 2, 9, 28, 15, 58, 3, 60, 2, 14};

    remove_smallest_duplicate(data);
}
template<typename T, class Hash=std::hash<T>>
class smallest_dup_remover
{
public:
    explicit smallest_dup_remover(std::size_t max_size) 
    {
        while(m_mask < max_size)
            m_mask *= 2;

        m_status.resize(m_mask);
        m_vals.resize(m_mask);

        --m_mask;
    }

    void operator()(std::vector<T> &vals)
    {
        std::fill(std::begin(m_status), std::end(m_status), 0);
        bool has = false;
        T min_;
        std::vector<T> spillover;
        spillover.reserve(vals.size());
        for(auto v: vals)
        {
            const std::size_t pos = m_hash(v) & m_mask;
            char &status = m_status[pos];
            switch(status)
            {
            case 0:
                status = 1;
                m_vals[pos] = v;
                break;
            case 1:
                if(m_vals[pos] == v)
                {
                    status = 2;
                    min_ = has? std::min(min_, v): v;
                    has = true;
                }
                else
                    spillover.push_back(v);
                break;
            case 2:
               if(m_vals[pos] != v)
                    spillover.push_back(v);
            }
        }
        std::sort(std::begin(spillover), std::end(spillover));
        auto it = std::adjacent_find(std::begin(spillover), std::end(spillover));
        if(has && it == std::end(spillover))
            remove_min(vals, min_);
        else if(has && it != std::end(spillover))
            remove_min(vals, std::min(min_, *it));
        else if(!has && it != std::end(spillover))
            remove_min(vals, *it);
    }

private:
    void remove_min(std::vector<T> &vals, T t)
    {
        vals.erase(std::find(vals.begin(), vals.end(), t)); 
    }

private:
    size_t m_mask = 1;
    std::vector<char> m_status;
    std::vector<T> m_vals;
    Hash m_hash;
};