在(任意大)流中搜索精确的字符串匹配-C ++

时间:2019-07-19 09:45:50

标签: c++ algorithm string-matching

我正在构建一个用于字符串匹配的简单多服务器。我通过使用套接字同时选择多个客户端。服务器要做的唯一工作是:客户端连接到服务器,并通过网络套接字以流的形式发送针(大小小于10 GB)和干草堆(任意大小)。针和干草堆是任意的二进制数据。

服务器需要在干草堆中搜索所有出现的针头(作为精确的字符串匹配项),然后将许多针头匹配项发送回客户端。服务器需要动态处理客户端,并能够在合理的时间内处理任何输入(这是搜索算法必须具有线性时间复杂度)。

要做到这一点,我显然需要将干草堆切成小块(可能比针头小),以便在它们通过网络插座时进行处理。那就是我需要一种能够处理字符串的搜索算法,该字符串被分割成多个部分并在其中进行搜索,就像strstr(...)一样。

我找不到任何标准的C或C ++库函数,也找不到可以部分处理字符串的Boost库对象。如果我没记错的话,当整个干草堆都位于连续的内存块中时,strstr(),string.find()和Boost search / knuth_morris_pratt.hpp中的算法只能处理搜索。还是有一些技巧可以用来按丢失的部分搜索字符串?你们知道吗,任何C / C ++库都可以应付如此大的麻烦。能够处理干草堆流或按部分在干草堆中进行搜索?

我没有通过谷歌搜索找到任何有用的库,因此我被迫创建自己的Knuth Morris Pratt算法变体,该算法能够记住其自身的状态(如下所示)。但是,我认为它不是最佳解决方案,因为我认为调整良好的字符串搜索算法肯定会表现更好,并且以后进行调试时也不必担心。

所以我的问题是: 除了创建自己的搜索算法之外,还有其他一些更优雅的方法来按部就班地搜索大型干草堆吗?有什么技巧可以使用标准的C字符串库吗?是否有一些专门用于此类任务的C / C ++库?

这是我的中端KMP算法的(一部分)代码:

#include <cstdlib>
#include <cstring>
#include <cstdio>

class knuth_morris_pratt {
    const char* const needle;
    const size_t needle_len;
    const int* const lps; // a longest proper suffix table (skip table)

// suffix_len is an ofset of a longest haystack_part suffix matching with
// some prefix of the needle. suffix_len myst be shorter than needle_len.
// Ofset is defined as a last matching character in a needle.
    size_t suffix_len;
    size_t match_count; // a number of needles found in haystack

public:
    inline knuth_morris_pratt(const char* needle, size_t len) : 
            needle(needle), needle_len(len),
            lps( build_lps_array() ), suffix_len(0),
            match_count(len == 0 ? 1 : 0)    {  }
    inline ~knuth_morris_pratt() {  free((void*)lps);   }

    void search_part(const char* haystack_part, size_t hp_len); // processes a given part of the haystack stream
    inline size_t get_match_count() { return match_count; }

private:
    const int* build_lps_array();
};

// Worst case complexity: linear space, linear time

// see: https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/
// see article: KNUTH D.E., MORRIS (Jr) J.H., PRATT V.R., 1977, Fast pattern matching in strings
void knuth_morris_pratt::search_part(const char* haystack_part, size_t hp_len) {
    if(needle_len == 0) {
        match_count += hp_len;
        return;
    }

    const char* hs = haystack_part;

    size_t i = 0; // index for txt[]
    size_t j = suffix_len; // index for pat[]
    while (i < hp_len) {
        if (needle[j] == hs[i]) {
            j++;
            i++;
        }

        if (j == needle_len) {
            // a needle found
            match_count++;
            j = lps[j - 1];
        }
        else if (i < hp_len && needle[j] != hs[i]) {
            // Do not match lps[0..lps[j-1]] characters,
            // they will match anyway
            if (j != 0)
                j = lps[j - 1];
            else
                i = i + 1;
        }
    }

    suffix_len = j;
}

const int* knuth_morris_pratt::build_lps_array() {
    int* const new_lps = (int*)malloc(needle_len);
//    check_cond_fatal(new_lps != NULL, "Unable to alocate memory in knuth_morris_pratt(..)");

    // length of the previous longest prefix suffix
    size_t len = 0;
    new_lps[0] = 0; // lps[0] is always 0

    // the loop calculates lps[i] for i = 1 to M-1
    size_t i = 1;
    while (i < needle_len) {
        if (needle[i] == needle[len]) {
            len++;
            new_lps[i] = len;
            i++;
        }
        else // (pat[i] != pat[len])
        {
            // This is tricky. Consider the example.
            // AAACAAAA and i = 7. The idea is similar
            // to search step.
            if (len != 0) {
                len = new_lps[len - 1];

                // Also, note that we do not increment
                // i here
            }
            else // if (len == 0)
            {
                new_lps[i] = 0;
                i++;
            }
        }
    }

    return new_lps;
}


int main() 
{
    const char* needle = "lorem";
    const char* p1 = "sit voluptatem accusantium doloremque laudantium qui dolo";
    const char* p2 = "rem ipsum quia dolor sit amet";
    const char* p3 = "dolorem eum fugiat quo voluptas nulla pariatur?";
    knuth_morris_pratt searcher(needle, strlen(needle));
    searcher.search_part(p1, strlen(p1));
    searcher.search_part(p2, strlen(p2));
    searcher.search_part(p3, strlen(p3));

    printf("%d \n", (int)searcher.get_match_count());

    return 0;
}

2 个答案:

答案 0 :(得分:2)

您可以看看BNDM,它的性能与KMP相同:

  • O(m)进行预处理
  • O(n)进行匹配。

它用于nrgrep,其源可以在here中找到,其中包含C源。

BNDM算法的C源here

有关更多信息,请参见here

答案 1 :(得分:2)

如果我很好地理解了您的问题,那么您想搜索是否部分收到的大std::string包含一个子字符串。

如果是这种情况,我认为您可以为每次迭代存储两个相邻接收数据包之间的重叠部分。然后,您只需要检查每次迭代,以确保重叠或数据包都包含所需的模式即可找到。

在下面的示例中,我考虑使用以下contains()函数来搜索std::string中的模式:

bool contains(const std::string & str, const std::string & pattern)
{
    bool found(false);

    if(!pattern.empty() && (pattern.length() < str.length()))
    {
        for(size_t i = 0; !found && (i <= str.length()-pattern.length()); ++i)
        {
            if((str[i] == pattern[0]) && (str.substr(i, pattern.length()) == pattern))
            {
                found = true;
            }
        }
    }

    return found;
}

示例:

std::string pattern("something");            // The pattern we want to find
std::string end_of_previous_packet("");      // The first part of overlapping section
std::string beginning_of_current_packet(""); // The second part of overlapping section

std::string overlap;                         // The string to store the overlap at each iteration

bool found(false);

while(!found && !all_data_received())          // stop condition
{
    // Get the current packet
    std::string packet = receive_part();

    // Set the beginning of the current packet
    beginning_of_current_packet = packet.substr(0, pattern.length());

    // Build the overlap
    overlap = end_of_previous_packet + beginning_of_current_packet;

    // If the overlap or the packet contains the pattern, we found a match
    if(contains(overlap, pattern) || contains(packet, pattern))
        found = true;

    // Set the end of previous packet for the next iteration
    end_of_previous_packet = packet.substr(packet.length()-pattern.length());
}

当然,在此示例中,我假设方法receive_part()已经存在。 all_data_received()函数也是如此。 这只是一个说明这一想法的例子。

我希望它能帮助您找到解决方案。