我有大量(XXM-XXXM)字符串看起来像(一个小样本):
我不知道所有可能的错误字符串,也不知道其排列。我想将所有类似的错误组合在一起,并生成一些统计信息,显示每个错误字符串组的错误计数。
所以,基本上,我想将最相似的字符串组合在一起,字符串可以属于多个组。
谢谢!
答案 0 :(得分:3)
免责声明:我以前从未解决过这样的问题。
我可以想出几种方法来思考你的问题:
set(line1.split) & set(line2.split)
- 结果集中的元素数量是这两条线接近程度的指标。一些python代码可能如下所示:
import fileinput
CLUSTER_COUNT = 5
MAX_DISTANCE = 5
def main():
clusters = [Cluster() for i in range(CLUSTER_COUNT)]
MAXDISTANCE = 3
for line in fileinput.input():
words = set(line.split())
cluster = sorted(clusters, key=lambda c: c.distanceTo(words))[0]
cluster.addLine(words, line)
# print out results (FIXME: write clusters to separate files)
for cluster in clusters:
print "CLUSTER:", cluster.intersection
for line in cluster.lines:
print line
print "-" * 80
print
class Cluster(object):
def __init__(self):
self.intersection = set()
self.lines = []
def distanceTo(self, words):
if len(self.intersection) == 0:
return MAX_DISTANCE
return len(words) - len(self.intersection & words)
def addLine(self, words, line):
self.lines.append(line)
if len(self.intersection) == 0:
self.intersection = words
else:
self.intersection = self.intersection & words
if __name__ == '__main__':
main()
如果在主数据上运行它,最终应该有几个集群。注意:更改代码以将群集写入单独的文件。我想你会想要以递归的方式再次通过代码运行集群,直到找到你感兴趣的子集为止。
答案 1 :(得分:3)
我在这里要做的是写一个参数识别器。这会将以下子字符串(当用空格包围时)识别为参数:
毫无疑问会有更多,但名单不应该变得太大。然后用占位符替换每个参数:%d,%url,%phpfile。现在你可以对字符串进行排序。
您可以通过查看很少出现的输出字符串来查找无法识别的参数类型。例如,如果某一时间存在参数类型 h:m:s ,则包含此未取消参数的字符串将是唯一的,或几乎是这样,并且您可以通过简单地找到此新参数类型关注100个左右“最接近独特”字符串的列表。然后将 h:m:s 添加到列表中,用%time替换所有此类事件,然后再次运行。
答案 2 :(得分:2)
在CRM114 the Controllable Regex Mutilator的页面上阅读了更多内容后:
垃圾邮件是CRM114的主要目标,但它不是专门的电子邮件工具。 CRM114已用于对网页,简历,博客条目,日志文件和许多其他内容进行排序。准确度可高达99.9%。换句话说,CRM114学习,并且学得很快。
简而言之,这可能正是您所需要的。您可以依靠它进行优化。
答案 3 :(得分:1)
有趣的问题......只是一个可以很快编码的自发想法:
性能(非常粗略估计):在1.阶段,它足以对您的数据进行采样,在2.阶段您必须按顺序处理每一行,因此您在O(n)范围内,其中n是行数在您的数据集中(在1.阶段使用O(1)插入的哈希映射,在2.阶段使用O(1)测试)。记忆消耗取决于不同单词(1.阶段)和不同句子(2阶段)的数量。如果您的单词变化较大,则在第一阶段计算出现次数的字典可能会出现问题。
在Python中,作为数据结构,我会尝试使用特殊(高性能)dict数据类型Counter。
答案 4 :(得分:1)
为此,你需要找到一些想法来映射单个字符串(它只是反向的散列技术,更强调避免使用collisons)但我猜这里几乎相同字符串的冲突是可以的。所以映射函数应该是字母的位置属性函数,即字符串的长度。
当映射完成后,我们创建一个值为范围组1的桶:(0-10)(或根据值这样)组2:(10-20).....等等......因为我们说类似的字符串应该具有相似的值,所以类似的字符串放在同一个桶上
因此,每当遇到新消息时,我们都会用数字等价物将其映射到适当的存储桶中。
答案 5 :(得分:0)
如果对输入没有任何假设,很难回答。
我的方法是:
如果您想要将一组特定的“相似”字符串组合在一起,则只能告诉您(无需假设)。必须有一定数量的错误消息类型,因此在经过几次迭代后,这应该会产生足够好的结果。
另一种方法可能是,假设相似性基于字符串中的数字和IP地址,就是搜索那些,并使用这些数字对项目进行分类。这再次涉及对您的规则更具体,但可能会让您免于迭代的规则制定(如果假设实际上有效)。
可以考虑测量字符串之间的levenshtein距离(或使用类似的算法),但是对于需要太多步骤的分类。
答案 6 :(得分:0)
嗯,为什么重新发明轮子 - 看一下splunk的免费版本,为这类任务设计 。
答案 7 :(得分:0)
如果由我决定,我会使用python和正则表达式。免责声明:我从未在你所谈论的规模上使用过python。
以下是样本输出中的前两项,用正则表达式表示法重写:
没那么糟,对吧?您可以将它们用作顶级存储桶的“密钥”。
然后,将给定字符串与存储桶匹配的代码变得非常简单:
for bucket in buckets:
if re.match(bucket.regex, s):
bucket.matchingStrings.append(s)
break
# else if no buckets match, generate a new bucket/regex for s
生成这些正则表达式将是棘手的部分。您需要有规则来选择需要概括的字符串部分。在您的情况下,通用部分似乎是数字,IP地址和文件名。你必须为每个场景提出正确的表达式,但是这里有一个简单的替换,它用字符串中的数字替换表示数字的正则表达式。
pattern = r"\d+"
re.sub(pattern, pattern, "Failed to count from 0 to 600")
# returns r"Failed to count from \d+ to \d+"
我敢打赌,你会在\ d +替换方面取得相当远的进展。
答案 8 :(得分:0)
本身不是一个算法解决方案,但可能会派上用场,并且在计算复杂性方面给你一些更多的自由:日志文件分析是MapReduce实现Hadoop及其朋友的主要用例之一。因此,如果您遇到可扩展性问题,您可能会开始考虑在可管理的子集上解决问题(映射步骤),然后将子集的输出组合成一个(reduce步骤)。例如,您可能会在子集中找到存储桶,然后比较所有存储桶集并合并相似的存储桶。
答案 9 :(得分:0)
从您最近的编辑中,您似乎在进行交叉时比较字符串。不要比较字符串;只是比较哈希。 64位哈希冲突的可能性基本为零。这将通过节省许多缓存未命中来加速字符串比较时间。
答案 10 :(得分:0)
我做了一些相似的事情,我需要从数据库中找到类似的字符串。带有一些附加功能的Trie将为此提供很多帮助。 try的常见实现仅支持插入和搜索/搜索前缀。但是,也可以计算trie中所有数据的Levensthein距离,然后使用它来获得最接近的字符串。
在trie中实现的Levensthein算法也可用于计算两个字符串之间的变化并生成模板。像这样:
similars = get_similar_strings( input_string, max_distance );
for each similar in similars do
if is string then
//construct template from string
else
// increase count
done
答案 11 :(得分:0)
我会做这样的事情。
map<string, int> mStringToInt;
struct structOneRecord
{
vector<int> vWords;
vector<vector<int>> vLines; // All the Lines under this record
vector<int> vIndexOfMutableWords;
bool operator < (const structOneRecord &n) const
{
if(vWords.size() != n.vWords.size())
return vWords.size() < n.vWords.size();
else
{
// Count diferences
vector<int> vCurrentIndexs;
for(int i=0; i<vWords.size(); i++)
{
if(vWords[i] != n.vWords[i])
vCurrentIndexs.push_back(i);
}
if(vCurrentIndexs.size() == 0)
return false;
int iHalf = vWords.size() / 2; // The diferences can't be bigger than hald the phrase
if(vCurrentIndexs.size() < iHalf)
{
if(vIndexOfMutableWords.size() == 0)
return false;
else
{
if(vIndexOfMutableWords.size() == vCurrentIndexs.size())
{
for(int i=0; i<vIndexOfMutableWords.size(); i++)
{
if(vIndexOfMutableWords[i] != vCurrentIndexs[i])
vWords[vCurrentIndexs[0]] < n.vWords[vCurrentIndexs[0]]; // Not equal
}
}
}
}
return vWords[vCurrentIndexs[0]] < n.vWords[vCurrentIndexs[0]];
}
}
};
vector<string> SplitString(const string &strInput, char cDelimiter, bool bSkipEmpty)
{
vector<string> vRetValue;
stringstream ss(strInput);
string strItem;
while(std::getline(ss, strItem, cDelimiter))
{
// Skip Empty
if(bSkipEmpty && strItem.size()==0)
continue;
vRetValue.push_back(strItem);
}
return vRetValue;
}
void main()
{
// To Test
vector<string> vInput;
vInput.push_back("Connection to 11.22.33.44 port 3940 timed out client 1.2.3.4 source port 3940");
vInput.push_back("Error loading page somepage.php by client 2.3.4.5");
vInput.push_back("Load of page someotherpage.php by client 2.3.4.8 failed due to error 4930");
vInput.push_back("Connection to 11.22.33.55 port 3829 timed out client 1.2.3.6 source port 3944");
vInput.push_back("Load of page alt.php by client 2.3.4.92 failed due to error 3829");
vInput.push_back("Load of page alt2.php by client 2.3.4.95 failed due to error 3829");
set<structOneRecord> sRecords;
for(int i=0; i<vInput.size(); i++)
{
vector<string> vWords = CMkDevStringUtilities::SplitString(vInput[i], ' ', true);
structOneRecord stRecord;
stRecord.vWords.resize(vWords.size());
for(int j=0; j<vWords.size(); j++)
{
map<string, int>::iterator mIte = mStringToInt.find(vWords[j]);
if(mIte == mStringToInt.end())
mIte = mStringToInt.insert(mStringToInt.begin(), make_pair(vWords[j], mStringToInt.size()));
stRecord.vWords[j] = mIte->second;
}
set<structOneRecord>::iterator sIte = sRecords.find(stRecord);
if(sIte != sRecords.end())
{
sIte->vLines.push_back(stRecord.vWords);
if(sIte->vIndexOfMutableWords.size() == 0)
{
// Count diferences
vector<int> vCurrentIndexs;
for(int i=0; i<stRecord.vWords.size(); i++)
{
if(sIte->vWords[i] != stRecord.vWords[i])
vCurrentIndexs.push_back(i);
}
sIte->vIndexOfMutableWords = vCurrentIndexs;
}
}
else
{
stRecord.vLines.push_back(stRecord.vWords);
sRecords.insert(stRecord);
}
}
}
通过为每个记录重建一个带有vWords指向的子串的字符串(并用'%%'替换Mutable单词中的索引处的字符串),这将为您提供一个可以轻松打印为输出的输出。也可以给你按记录“类型”排序的行。
*编辑 忘了在每个structOneRecord下提到vLines.size()给你错误计数。
答案 12 :(得分:0)
我认为需要一些手动干预,频率计数将是最好的方法。