我正在构建一个用于字符串匹配的简单多服务器。我通过使用套接字同时选择多个客户端。服务器要做的唯一工作是:客户端连接到服务器,并通过网络套接字以流的形式发送针(大小小于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;
}
答案 0 :(得分:2)
您可以看看BNDM,它的性能与KMP相同:
它用于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()
函数也是如此。 这只是一个说明这一想法的例子。
我希望它能帮助您找到解决方案。