如何仅使用stl算法实现此功能

时间:2014-01-20 08:05:35

标签: c++ stl iterator stl-algorithm

我必须实现一个函数,它以字典顺序在控制台上打印每个字符串,作为第一个字母,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);

但它不起作用。

5 个答案:

答案 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_tostd::string::frontstd::logical_andstd::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(正如我们对常规文本所期望的那样)将占主导地位。

编辑:正如Jerry Coffin的回答指出的那样,如果弄乱输入向量是可以接受的(它是原始问题中的const引用),那么你可以通过使用std::partition而没有临时副本而离开 - kudos他想到了这一点。

答案 4 :(得分:0)

共有3种解决方案:

  1. 排序然后复制满足您要求的元素,复杂N * log(N)+ N
  2. 复制满足您要求的元素,然后排序,复杂度N + n * log(n)
  3. 使用std :: lowerbound,复杂度N * log(N)+ 2 * log(N)+ n
  4. 对范围进行排序和搜索

    在所有情况下,N是向量的大小,n是满足谓词的元素数。通常n <= N并且取决于您的数据集(正常的英文文本),它可以是&lt;&lt; Ñ