如何切断字符串的一部分,集合中的每个字符串都有

时间:2016-08-24 11:06:43

标签: c++ string c++11 c++14 matching

我目前的问题如下: 我有一个std::vector个文件的完整路径名。 现在我想切断所有字符串的公共前缀。

实施例

如果我在向量中有这3个字符串:

/home/user/foo.txt
/home/user/bar.txt
/home/baz.txt

我想从向量中的每个字符串中删除/home/

问题

是否有任何方法可以实现这一目标? 我想要一个删除所有字符串的公共前缀的算法。 我目前只有一个想法,用O( n m )解决这个问题, n 字符串和 m 是最长的字符串长度,通过char遍历每个字符串与每个其他字符串char。 是否有更快或更优雅的方式来解决这个问题?

6 个答案:

答案 0 :(得分:3)

这可以完全用std :: algorithms来完成。

简介:

  1. 如果尚未排序,则对输入范围进行排序。排序范围中的第一个和最后一个路径 将是最不相似的。最好的情况是O(N),最坏的情况是O(N + N.logN)

  2. 使用std::mismatch来确定之间的大型公共序列 两条最不相似的路径[无足轻重]

  3. 遍历每个路径,删除第一个COUNT个字符,其中COUNT是最长公共序列中的字符数。 O(N)

  4. 最佳案例时间复杂度:O(2N),最差情况O(2N + N.logN)(有人可以检查吗?)

    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <vector>
    
    std::string common_substring(const std::string& l, const std::string& r)
    {
        return std::string(l.begin(),
                           std::mismatch(l.begin(), l.end(),
                                         r.begin(), r.end()).first);
    }
    
    std::string mutating_common_substring(std::vector<std::string>& range)
    {
        if (range.empty())
            return std::string();
        else
        {
            if (not std::is_sorted(range.begin(), range.end()))
                std::sort(range.begin(), range.end());
            return common_substring(range.front(), range.back());
        }
    }
    
    std::vector<std::string> chop(std::vector<std::string> samples)
    {
        auto str = mutating_common_substring(samples);
        for (auto& s : samples)
        {
            s.erase(s.begin(), std::next(s.begin(), str.size()));
        }
        return samples;
    }
    
    int main()
    {
        std::vector<std::string> samples = {
            "/home/user/foo.txt",
            "/home/user/bar.txt",
            "/home/baz.txt"
        };
    
        samples = chop(std::move(samples));
    
        for (auto& s : samples)
        {
            std::cout << s << std::endl;
        }   
    }
    

    预期:

    baz.txt
    user/bar.txt
    user/foo.txt
    

    这里有一个替代的`common_substring&#39;这不需要排序。时间复杂度在理论上是O(N),但在实践中它是否更快,你必须检查:

    std::string common_substring(const std::vector<std::string>& range)
    {
        if (range.empty())
        {
            return {};
        }
    
        return std::accumulate(std::next(range.begin(), 1), range.end(), range.front(),
                               [](auto const& best, const auto& sample)
                               {
                                   return common_substring(best, sample);
                               });
    }
    

    更新

    除了优雅之外,这可能是最快的方式,因为它避免了任何内存分配,就地执行所有转换。对于大多数架构和样本规模而言,这比任何其他性能考虑更重要。

    #include <iostream>
    #include <vector>
    #include <string>
    
    void reduce_to_common(std::string& best, const std::string& sample)
    {
        best.erase(std::mismatch(best.begin(), best.end(),
                                 sample.begin(), sample.end()).first,
                   best.end());
    
    }
    
    void remove_common_prefix(std::vector<std::string>& range)
    {
        if (range.size())
        {
            auto iter = range.begin();
            auto best = *iter;
            for ( ; ++iter != range.end() ; )
            {
                reduce_to_common(best, *iter);
            }
    
            auto prefix_length = best.size();
    
            for (auto& s : range)
            {
                s.erase(s.begin(), std::next(s.begin(), prefix_length));
            }
        }
    }
    
    
    int main()
    {
        std::vector<std::string> samples = {
            "/home/user/foo.txt",
            "/home/user/bar.txt",
            "/home/baz.txt"
        };
    
        remove_common_prefix(samples);
    
        for (auto& s : samples)
        {
            std::cout << s << std::endl;
        }   
    }
    

答案 1 :(得分:2)

你只需要迭代每个字符串。你只能通过利用前缀只能缩短的事实来避免不必要地遍历整个字符串的长度:

#include <iostream>
#include <string>
#include <vector>

std::string common_prefix(const std::vector<std::string> &ss) {
    if (ss.empty())
        // no prefix
        return "";

    std::string prefix = ss[0];

    for (size_t i = 1; i < ss.size(); i++) {
        size_t c = 0; // index after which the string differ
        for (; c < prefix.length(); c++) {
            if (prefix[c] != ss[i][c]) {
                // strings differ from character c on
                break;
            }
        }

        if (c == 0)
            // no common prefix
            return "";

        // the prefix is only up to character c-1, so resize prefix
        prefix.resize(c);
    }

    return prefix;
}

void strip_common_prefix(std::vector<std::string> &ss) {
    std::string prefix = common_prefix(ss);
    if (prefix.empty())
        // no common prefix, nothing to do
        return;

    // drop the common part, which are always the first prefix.length() characters
    for (std::string &s: ss) {
        s = s.substr(prefix.length());
    }
}

int main()
{
    std::vector<std::string> ss { "/home/user/foo.txt", "/home/user/bar.txt", "/home/baz.txt"};
    strip_common_prefix(ss);
    for (std::string &s: ss)
        std::cout << s << "\n";
}

根据Martin Bonner's answer的提示,如果您对输入有更多的先验知识,则可以实施更有效的算法。 特别是,如果您知道输入已排序,则只需比较第一个和最后一个字符串即可(参见Richard's answer)。

答案 2 :(得分:2)

您必须搜索列表中的每个字符串。但是,您不需要比较每个字符串中的所有字符。公共前缀只能缩短,因此您只需要与#34;到目前为止的公共前缀&#34;进行比较。我不认为这会改变大O的复杂性 - 但它会对实际速度产生很大的影响。

此外,这些看起来像文件名。它们是否排序(请记住,许多文件系统倾向于按排序顺序返回内容)?如果是这样,您只需要考虑第一个和最后一个元素。如果它们可能 pr 主要是有序,那么考虑第一个和最后一个的公共前缀,然后迭代所有其他字符串,根据需要进一步缩短前缀。

答案 3 :(得分:1)

i - 找到文件夹深度最小的文件(即baz.txt) - 它的根路径是home ii - 然后浏览其他字符串以查看它们是否以该根开头。 iii - 如果是,则从所有字符串中删除root。

答案 4 :(得分:1)

std::size_t index=0;开始。扫描列表以查看该索引处的字符是否匹配(注意:结束时不匹配)。如果是,请提前索引并重复。

完成后,index将具有前缀长度的值。

此时,我建议您编写或找到string_view类型。如果您这样做,只需为每个字符string_view创建一个str,其开头/结尾为index, str.size()

总费用:O(|prefix|*N+N),这也是确认答案是否正确的费用。

如果您不想撰写string_view,只需在向量中的每个str.erase(str.begin(), str.begin()+index)上致电str

总费用为O(|total string length|+N)。必须访问前缀才能确认它,然后必须重写字符串的尾部。

现在广度优先的成本是地方性的,因为你在整个地方触摸记忆。实际上,在块中执行它可能会更有效率,在这些块中,您扫描前K个字符串长度Q并找到公共前缀,然后链接该公共前缀加上下一个块。这不会改变O符号,但会改善内存引用的局部性。

答案 5 :(得分:0)

for(vector<string>::iterator itr=V.begin(); itr!=V.end(); ++itr)
    itr->erase(0,6);