我必须实现一个函数,它以字典顺序在控制台上打印每个字符串,作为第一个字母,char c
,仅使用stl算法。
以下是我的想法:
void f(const std::vector<std::string>& vs, const char c)
{
std::vector<std::string> tmp = vs;
std::sort(tmp.begin(), tmp.end());
std::ostream_iterator<std::string> out(std::cout, "\n");
std::copy_if(tmp.begin(), tmp.end(), out, *predicate*);
}
作为谓词,我想:
//*(tmp.begin()->begin()) == c);
但它不起作用。
答案 0 :(得分:5)
你得到的答案简单而整洁,但如果你有大量不适合过滤器的数据(在这种情况下以'c'开头),效率可能非常低。
我看到两个基本问题。首先,他们对所有数据进行排序,无论它是否适合过滤器。这本身就是非常低效的。其次,然后他们使用copy_if
来做数据的过滤副本 - 但是copy_if没有做任何事情来利用排序。它进行线性搜索,因此它查看所有 all 输入数据,包括正确的算法已经知道的很多不值得考虑的事项(例如,一旦它得到以'开头的' d',它也可以停止,因为没有更多的数据值得考虑。)
或者,他们首先进行过滤,但是通过将所有相关数据复制到新创建的向量,然后对该数据副本进行排序来进行过滤。这在速度方面可能相当有效,但可能会占用相当多的额外内存。
我认为最好先过滤,但不进行不必要的复制,然后只对适合过滤器的数据进行排序,最后将排序后的数据复制到输出中。在这种情况下,我们可以使用std::partition
来有效地过滤数据。
auto end = std::partition(in.begin(), in.end(),
[](std::string const &s) { return s[0] == 'c';});
std::sort(in.begin(), end);
std::copy(in.begin(), end, std::ostream_iterator<std::string>(std::cout, "\n"));
除了std::partition
的异常可怕的实现之外,过滤然后排序应始终至少与排序然后过滤一样快 - 如果大量原始输入被过滤掉,首先过滤可能是明显更快。与创建过滤后的副本,然后对副本进行排序相比,它可以明显节省相当多的内存。在大多数情况下,它也会快得多。分区只需要交换字符串,而不是复制它们,这通常要快得多(当std::string
使用短字符串优化时,主要的例外是短字符串)。
答案 1 :(得分:4)
我认为排序所有元素然后仅打印以c
开头的元素是浪费。那么只对那些进行排序呢?
struct first_char_is {
char x;
first_char_is(char x) : x(x) {}
bool operator()(const std::string& s) {
return s.size() > 0 && s[0] == x;
}
};
void f(const std::vector<std::string>& vs, const char c)
{
std::vector<std::string> tmp;
std::copy_if(vs.begin(), vs.end(), std::back_inserter(tmp),
first_char_is(c));
std::sort(tmp.begin(), tmp.end());
std::ostream_iterator<std::string> out(std::cout, "\n");
std::copy(tmp.begin(), tmp.end(), out);
}
在C ++中,字符串是可变的,COW字符串实现有自己的问题。这意味着当您复制字符串向量时,所有字符串数据也会重复。为了节省内存,另一种方法是将索引保留并对原始数组进行排序,但我不确定这是否符合“仅限stl”的人工要求(无论这意味着什么)。
struct IndirectComp {
const std::vector<std::string>& vs;
IndirectComp(const std::vector<std::string>& vs) : vs(vs) {}
const bool operator()(int a, int b) {
return vs[a] <= vs[b];
}
};
void f(const std::vector<std::string>& vs, const char c)
{
std::vector<int> ix;
for (int i=0,n=vs.size(); i<n; i++) {
if (vs[i].size() && vs[i][0] == c) {
ix.push_back(i);
}
}
std::sort(ix.begin(), ix.end(), IndirectComp(vs));
for (int i=0,n=ix.size(); i<n; i++) {
std::cout << vs[ix[i]] << "\n";
}
}
答案 2 :(得分:1)
最简单的方法是使用lambda作为谓词:
void f(std::vector<std::string> vs, const char c)
{
std::sort(vs.begin(), vs.end());
std::ostream_iterator<std::string> out(std::cout, "\n");
std::copy_if(vs.begin(), vs.end(), out,
[c](const std::string & s){return !s.empty() && s.front() == c;}
);
}
仅使用<algorithm>
编写谓词是不可能的。但是,可以使用std::bind
中的std::equal_to
,std::string::front
,std::logical_and
,std::string::empty
和<functional>
重建lambda。但是,这会使您的代码变得非常复杂。
由于您已经在使用C ++ 11,我建议您使用lambdas。
答案 3 :(得分:1)
就像Benjamin Lindley一样,我认为接受的答案是次优的,这可能是一种更好的方法(未经测试,但你明白了):
void f(std::vector<std::string> vs, const char c)
{
std::vector<std::string> result;
std::copy_if(vs.begin(), vs.end(), std::back_inserter(result),
[c](const std::string& s) { return !s.empty() && s.front() == c; });
std::sort(result.begin(), result.end());
std::copy(result.begin(), result.end(), std::ostream_iterator(std::cout, "\n"));
}
如果我们假设输入向量具有N
条目,其中K
以字母c
开头,则执行O(N)
搜索/复制后跟{O(K.logK)
1}}(平均)排序,然后O(K)
“复制”到输出流。 Zeta答案中的方法首先排序O(N.logN)
,如果K << N
(正如我们对常规文本所期望的那样)将占主导地位。
std::partition
而没有临时副本而离开 - kudos他想到了这一点。
答案 4 :(得分:0)
共有3种解决方案:
在所有情况下,N是向量的大小,n是满足谓词的元素数。通常n <= N并且取决于您的数据集(正常的英文文本),它可以是&lt;&lt; Ñ