【问题标题】:Counting Duplicates in C++ - multiset?在 C++ 中计算重复项 - 多集?
【发布时间】:2021-08-29 17:33:54
【问题描述】:

UPD:-
值实例
2   3
3   2
5   1

我想将多重集中存在的所有实例的计数限制为 1。

#include<bits/stdc++.h>
using namespace std;
int main() {
    multiset<int> p1;
    
    p1.insert(5);
    p1.insert(2);
    p1.insert(3);
    p1.insert(3);
    p1.insert(2);
    p1.insert(2);
    
    for(auto itr : p1) {
        
        if(p1.count(itr) > 1)
        p1.erase(itr);

        cout << itr;
    }
    
}

如何解决这个问题?

【问题讨论】:

  • 您不能将范围循环与erase() (AFAIK) 结合使用。 erase() 使当前迭代器无效(但返回一个新迭代器)。所以,这会起作用:for (multiset&lt;int&gt;::iterator iter = p1.begin(); iter != p1.end();) { iter = p1.erase(iter); }(或者只是按照答案的建议去做:p1.clear();
  • 假设我在多重集中有 7 个 2 的实例和 3 个 5 的实例。我想设置一个 if 条件,即如果我遇到超过 1 个任意数字的实例,那么我会将其限制为该数字的 1 个实例。
  • 在这种情况下,您应该使用std::set&lt;int&gt;,因为这实际上符合您的要求。如果愿意,您还可以使用 std::map&lt;int, int&gt; 将键映射到出现次数。
  • 请不要将未决问题更新为新主题。这使得现有的答案不匹配。相反,为新问题打开一个新问题。

标签: c++ c++17 multiset


【解决方案1】:

我的评论:

在这种情况下,您应该使用std::set&lt;int&gt;,因为这实际上符合您的要求。如果愿意,您还可以使用 std::map&lt;int, int&gt; 将键映射到出现次数。

OP 回复:

您能否将此添加到完整的答案中,以便我可以接受这个问题?

我们开始吧:

只过滤重复项:

#include <iostream>
#include <set>

int main()
{
  int sample[] = { 5, 2, 3, 3, 2, 2 };
  // add all values at most once
  using Table = std::set<int>;
  Table table;
  for (int value : sample) table.insert(value);
  // output the result
  for (const Table::value_type& entry : table) {
    std::cout << "Value " << entry << "\n";
  }
}

输出:

Value 2
Value 3
Value 5

Demo on coliru


统计出现次数:

#include <iostream>
#include <map>

int main()
{
  int sample[] = { 5, 2, 3, 3, 2, 2 };
  // add all values at most once but count the number of occurrences
  using Table = std::map<int, unsigned>;
  Table table;
  for (int value : sample) ++table[value];
  // output the result
  for (const Table::value_type& entry : table) {
    std::cout << "Value " << entry.first << " (" << entry.second << " times)\n";
  }
}

输出:

Value 2 (3 times)
Value 3 (2 times)
Value 5 (1 times)

Demo on coliru

诀窍:

如果键不存在,std::map::operator[] 会插入一个元素。此元素(在本例中为 std::pair&lt;const int, unsigned&gt;)默认初始化为 { key, 0 }

所以,有两种情况:

  • 钥匙还没有:
    元素被创建为{ key, 0 },并且值(元素的.second)立即增加,从而导致{ key, 1 }
  • 钥匙已经在那里了:
    值(元素的.second)再次递增。

过滤重复项的变体:

这保留了原始输入顺序,但删除了重复(通过在单独的std::set 中记账)。

#include <iostream>
#include <set>
#include <vector>

int main()
{
  using Sample = std::vector<int>;
  Sample sample = { 5, 2, 3, 3, 2, 2 };
  // remove duplicates
  using Table = std::set<int>;
  Table table;
  Sample::iterator iterRead = sample.begin();
  Sample::iterator iterWrite = sample.begin();
  for (; iterRead != sample.end(); ++iterRead) {
    if (table.insert(*iterRead).second) *iterWrite++ = *iterRead;
  }
  sample.erase(iterWrite, sample.end());
  // output the result
  for (const Sample::value_type& entry : sample) {
    std::cout << "Value " << entry << "\n";
  }
}

输出:

Value 5
Value 2
Value 3

Demo on coliru

诀窍:

std::set::insert() 返回一对iteratorbool
iterator 指向集合中的键(已插入或已存在)。 bool 表示密钥是已插入 (true) 还是已经存在 (false)。

另一个技巧:

仅从std::vector 中删除每个找到的重复项会导致更差的复杂度 O(n²)。

因此,使用了两个迭代器,一个用于读取,一个用于写入。因此,尚未在簿记表中(因此第一次出现)的每个输入值都被写回,否则不写回。 因此,第一次出现的每个值都会移到开头并附加到每个第一次出现的先前值。此外,iterWrite 指向循环后最后写入的元素,可用于擦除其余部分(其中包含所有重复的左侧输入值)。

这个算法的复杂度是 O(n)——比简单的方法要好很多。

顺便说一句。标准算法std::remove(), std::remove_if() 也是这样做的。

因此,std::remove_if() 可以实现相同的算法:

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

int main()
{
  using Sample = std::vector<int>;
  Sample sample = { 5, 2, 3, 3, 2, 2 };
  // remove duplicates
  using Table = std::set<int>;
  Table table;
  Sample::iterator last
    = std::remove_if(sample.begin(), sample.end(),
      [&](int value) { return !table.insert(value).second; });
  sample.erase(last, sample.end());
  // output the result
  for (const Sample::value_type& entry : sample) {
    std::cout << "Value " << entry << "\n";
  }
}

输出:

如上

Demo on coliru

【讨论】:

  • 感谢您的回答。这也会对其他人有所帮助。
  • 您取消了复选标记?意外还是故意?
【解决方案2】:
#include <iostream>
#include <set>

using namespace std;

int main()
{
    multiset<int> p1; 

    p1.insert(5);
    p1.insert(2);
    p1.insert(3);
    p1.insert(4);
    p1.insert(2);
    p1.insert(2);

    for (auto iter = p1.begin(); iter != p1.end();)
    {
        p1.count(*iter) > 1 ? iter = p1.erase(iter) : iter++;
    }

    for (auto & iter : p1)
    {
        cout << iter << ", ";
    }
    
    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-24
    • 2014-01-28
    • 2012-03-24
    • 2021-08-29
    • 2017-05-07
    • 2021-06-04
    • 1970-01-01
    • 2014-04-08
    相关资源
    最近更新 更多