如何在 std::vector 中找到重复项<string>并返回它们的列表?
how to find duplicates in std::vector<string> and return a list of them?
所以如果我有一个单词向量,比如:
Vec1 = "words", "words", "are", "fun", "fun"
结果列表:"有趣","单词"
我试图确定哪些单词是重复的,并返回一个按字母顺序排列的矢量,其中有一个副本。我的问题是,我甚至不知道从哪里开始,我发现唯一接近它的是std::unique_copy
,它并不能完全满足我的需求。具体地说,我输入std::vector<std::string>
,但输出std::list<std::string>
。如果需要的话,我可以使用函子。
至少有人能把我推向正确的方向吗?我已经试过阅读stl文档,但我现在只是"大脑"堵塞了。
在3行中(不计算矢量和列表创建,也不计算可读性名称中多余的换行符):
vector<string> vec{"words", "words", "are", "fun", "fun"};
list<string> output;
sort(vec.begin(), vec.end());
set<string> uvec(vec.begin(), vec.end());
set_difference(vec.begin(), vec.end(),
uvec.begin(), uvec.end(),
back_inserter(output));
编辑
解决方案说明:
为了以后使用
set_difference()
,需要对矢量进行排序。uvec
集合将自动对元素进行排序,并消除重复项。output
列表将由vec - uvec
的元素填充。
- 制作一个空
std::unordered_set<std::string>
- 迭代向量,检查每个项是否都是集合的成员
- 如果它已经在集合中,则这是重复的,因此添加到您的结果列表中
- 否则,添加到集合中
由于您希望每个重复项在结果中只列出一次,因此您也可以对结果使用哈希集(而不是列表)。
IMO,Ben Voigt一开始提出了一个很好的基本想法,但我要提醒大家不要把他的措辞过于字面化。
特别是,我不喜欢在集合中搜索字符串,如果不存在,则将其添加到集合中,如果存在,则添加到输出中。这基本上意味着,每次我们遇到一个新单词时,我们都会搜索两次现有单词集,一次是检查一个单词是否存在,另一次是插入它,因为它不存在。大多数搜索基本上是相同的——除非其他线程在此期间对结构进行了变异(这可能会给出竞争条件)。
相反,我会先尝试将它添加到你所看到的一组单词中。这将返回一个pair<iterator, bool>
,其中bool
设置为true
,如果且仅当该值已插入,即之前不存在。这使我们可以将对现有字符串的搜索和对新字符串的插入合并为一个插入:
while (input >> word)
if (!(existing.insert(word)).second)
output.insert(word);
这也充分清理了流程,可以很容易地将测试转化为一个函数,然后我们可以将其与std::remove_copy_if
一起直接生成结果:
#include <set>
#include <iterator>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
class show_copies {
std::set<std::string> existing;
public:
bool operator()(std::string const &in) {
return existing.insert(in).second;
}
};
int main() {
std::vector<std::string> words{ "words", "words", "are", "fun", "fun" };
std::set<std::string> result;
std::remove_copy_if(words.begin(), words.end(),
std::inserter(result, result.end()), show_copies());
for (auto const &s : result)
std::cout << s << "n";
}
根据我是否更关心代码的简单性或执行速度,我可能会使用std::vector
而不是set
作为结果,并使用std::sort
和std::unique_copy
来产生最终结果。在这种情况下,我可能还会将show_copies
内部的std::set
替换为std::unordered_set
:
#include <unordered_set>
#include <iterator>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
class show_copies {
std::unordered_set<std::string> existing;
public:
bool operator()(std::string const &in) {
return existing.insert(in).second;
}
};
int main() {
std::vector<std::string> words{ "words", "words", "are", "fun", "fun" };
std::vector<std::string> intermediate;
std::remove_copy_if(words.begin(), words.end(),
std::back_inserter(intermediate), show_copies());
std::sort(intermediate.begin(), intermediate.end());
std::unique_copy(intermediate.begin(), intermediate.end(),
std::ostream_iterator<std::string>(std::cout, "n"));
}
这稍微复杂一点(长一整行!),但当/如果单词数量变得很大时,可能会快得多。还要注意,我使用std::unique_copy
主要是为了产生可见的输出。如果您只想在集合中获得结果,可以使用标准的唯一/擦除习惯用法来获得intermediate
中的唯一项。
就位(无额外存储)。没有字符串复制(结果列表除外)。一次分拣+一次通过:
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
vector<string> vec{"words", "words", "are", "fun", "fun"};
list<string> dup;
sort(vec.begin(), vec.end());
const string empty{""};
const string* prev_p = ∅
for(const string& s: vec) {
if (*prev_p==s) dup.push_back(s);
prev_p = &s;
}
for(auto& w: dup) cout << w << ' ';
cout << 'n';
}
您可以使用std::map来计算出现次数,然后依靠std::list::sort对结果的单词列表进行排序,从而获得一个非常干净的实现。例如:
std::list<std::string> duplicateWordList(const std::vector<std::string>& words) {
std::map<std::string, int> temp;
std::list<std::string> ret;
for (std::vector<std::string>::const_iterator iter = words.begin(); iter != words.end(); ++iter) {
temp[*iter] += 1;
// only add the word to our return list on the second copy
// (first copy doesn't count, third and later copies have already been handled)
if (temp[*iter] == 2) {
ret.push_back(*iter);
}
}
ret.sort();
return ret;
}
使用std::map似乎有点浪费,但它完成了任务。
这里有一个比其他人提出的更好的算法:
#include <algorithm>
#include <vector>
template<class It> It unique2(It const begin, It const end)
{
It i = begin;
if (i != end)
{
It j = i;
for (++j; j != end; ++j)
{
if (*i != *j)
{ using std::swap; swap(*++i, *j); }
}
++i;
}
return i;
}
int main()
{
std::vector<std::string> v;
v.push_back("words");
v.push_back("words");
v.push_back("are");
v.push_back("fun");
v.push_back("words");
v.push_back("fun");
v.push_back("fun");
std::sort(v.begin(), v.end());
v.erase(v.begin(), unique2(v.begin(), v.end()));
std::sort(v.begin(), v.end());
v.erase(unique2(v.begin(), v.end()), v.end());
}
它更好,因为它只需要swap
,而不需要辅助vector
进行存储,这意味着它在早期版本的C++中表现最佳,而且它不需要元素是可复制的。
如果你更聪明,我认为你也可以避免对向量进行两次排序。
- cppcheck在const std::string[]上引发警告
- 将std::string传递给WriteConsole API
- 为std::string的某个索引赋值
- std中有类似find_last_of的函数,而string中没有
- EASTL矢量<向量<int>>连续的
- 使用 std::string () const 函数启动线程或未来
- 使用char类型将decimal转换为string,将string转换为decimal
- 迭代和比较映射<字符串、矢量<string>> c++ 中的值
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 将向量解析<string>为字符串
- 'string.assign(string.data(), 5)' 是明确定义的还是 UB?
- 如何更改大小(std::string)
- "string.h"在构建适用于iOS的qt应用程序中找不到消息
- C++:如何将 unix 时间的字符串转换为 *tm?(使用时间错误:"cannot convert 'String' to 'tm*' ")
- std::string 的对象真的可以移动吗?
- 与'operator='不匹配(操作数类型'String'且"void")
- SegFault 同时使用 std::string::operator+= 和函数作为参数
- 无法从 std::string 中提取C++ Unicode 符号
- std::string 构造函数如何处理固定大小的 char[]?
- <string> 使用 for 循环写入向量