迭代排序列表并计算不同的数字

Iterate sorted list and count distinct numbers

我想迭代一个排序列表以获得不同数字的数量。

请在下方找到我的尝试。列表的大小为 k*k。 当列表排序时,我比较连续的项目以识别重复项。

int count_distinct(list<int> v)
{
    int num = k*k;
    std::list<int>::iterator it;
    it = v.begin();
    for (int a=0; a<k*k-1; a++)
    {
        if(*it == *it+1)
            num--;
        it++;
    }

    return num;
}

我不能更改列表,所以 std::list::unique() 不是一个选项。制作列表或独特项目的副本对我来说太慢了。

如何使用 std::set 获取唯一元素计数?

size_t count_distinct(const list<int>& v)
{    
    std::set<int> temp (v.begin(), v.end());

    return temp.size(); 
}

假设您要查找该列表中唯一整数的数量,并且该列表未排序,您可以使用临时集或 unordered_set,如下所示:

size_t count_distinct(list<int> v)
{
    std::unordered_set<int> distinct;
    for(auto &x : v)
    {
        distinct.insert(x);
    }
    return distinct.size();
}

这是一个提取所有唯一值的容器的解决方案 (因为你说你想在之后使用它们):

计算个唯一值的方法:

template < typename T >
size_t count_unique(const std::list<T> & input)
{
    std::set<T> unique(input.begin(), input.end());
    return unique.size();
}

提取唯一值列表的方法:

template < typename T >
void unique(const std::list<T> & input, std::list<T> & output)
{
    std::set<T> unique(input.begin(), input.end());   
    std::copy(unique.begin(), unique.end(), std::back_inserter(output));
}

示例程序:

int main(int argc, char** argv)
{
    std::list<int> list = { 1, 3, 4, 10, 3, 1, 6, 7 };
    std::list<int> out;

    std::cout << count_unique(list) << std::endl;

    unique(list, out);
    for (auto & x : out)
        std::cout << x << std::endl;
}

您可以使用 std::list<int>::unique() 获取 v 中的所有不同元素,并使用 size() 计算它们。 v 必须排序。检查 v 是否使用函数 std::is_sorted() 排序。如果不是 - 对其进行排序。这也意味着 count_distinct 不适用于常量列表对象。

size_t count_distinct(list<int>& v)
{
    if (!is_sorted(v.begin(), v.end()))
    {
        v.sort();
    }
    v.unique();
    return v.size();
}

您的代码存在以下问题:

  1. 您按值将容器传递给函数。您应该通过 const 引用传递它,以最大限度地减少速度和内存损失。
  2. 您的条件 *it == *it+1 始终为假(您比较 nn+1)。可能你想写 *it == *(it+1)std::listbidirectional iterators 而你不能 +1 它们。

代码应该是这样的:

size_t count_distinct(const std::list<int>& l) {
    if (l.empty()) return 0;

    size_t distinct = l.size();
    auto prev = l.begin();

    for (auto cur = std::next(prev); cur != l.end(); ++cur, ++prev) {
        if (*cur == *prev)
            --distinct;
    }

    return distinct;
}

或者您可以编写 std::unique 算法的修改版本:

template<class ForwardIt>
size_t unique_cnt(ForwardIt first, ForwardIt last) {
    if (first == last)
        return 0;

    size_t distinct = 1;    
    ForwardIt prev = first;

    while (++first != last) {
        if (!(*prev == *first)) {
            ++distinct;
        }
        prev = first;
    }
    return distinct;
}

然后简单地使用它

size_t distinct = unique_cnt(l.begin(), l.end());         

还有一个 std::unique_copy + 自定义迭代器的方法,但是看起来不够优雅。

对于排序数据,您可能不会比您尝试实施的直接方法更有效率。

我更喜欢这样的东西,因为我发现向上计数比向下计数更直观:

std::size_t count_unique_sorted(std::list<int> const& l) {
    if (l.empty()) return 0;
    std::size_t count = 1;
    auto previous_value = l.front();
    // TODO: hope that the compiler fixes that redundant first comparison...
    for (auto next_value : l) {
        if (next_value != previous_value) {
            // the value changed! increment count and update previous_value
            ++count;
            previous_value = next_value;
        }
    }
    return count;
}

您还可以使来自 C++17 的算法 std::unique_copy() algorithm to count instead of copy, by providing a custom OutputIterator. But this will have little benefit performance-wise versus the approach presented above. Maybe it will be worth revisiting, when the parallel implementations 变得可用。

这是一个例子:

template <typename T>
struct counter : public std::iterator<std::output_iterator_tag, T> {
    explicit counter(std::size_t& count) : count(count) {}
    counter& operator*() { return *this; }
    counter& operator++() { return *this; }
    void operator=(T const&) { ++count; }
private:
    std::size_t& count;
};

std::size_t count_unique_sorted2(std::list<int> const& l) {
    std::size_t count = 0;
    std::unique_copy(l.begin(), l.end(), counter<int>(count));
    return count;
}

请注意,在这两种情况下,您都希望将列表作为 const 引用而不是作为函数的副本传递。

如果您觉得这仍然很慢,请随时探索并行化的乐趣。这样做的好处可能取决于数据量和分布。所以你应该在那时开始一些系统的分析。

除非您需要对值进行大量重新排序,否则首先考虑将数据转储到 std::vector<int> 中。拥有随机访问迭代器可以简化事情,拥有更好的局部性也可以加快速度...