用替换分组数百万个字符串

时间:2011-06-01 07:34:31

标签: string

我有大量(XXM-XXXM)字符串看起来像(一个小样本):

我不知道所有可能的错误字符串,也不知道其排列。我想将所有类似的错误组合在一起,并生成一些统计信息,显示每个错误字符串组的错误计数。

所以,基本上,我想将最相似的字符串组合在一起,字符串可以属于多个组。

谢谢!

13 个答案:

答案 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)

我在这里要做的是写一个参数识别器。这会将以下子字符串(当用空格包围时)识别为参数:

  • 十进制数
  • a.b.c.d ,其中 a,b,c,d 是十进制数
  • 文件名 .PHP

毫无疑问会有更多,但名单不应该变得太大。然后用占位符替换每个参数:%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)

有趣的问题......只是一个可以很快编码的自发想法:

  • 确定每个单词是否为数据(11.22.33.55,3829,somepage.php等)或 description (加载,连接,页面等)通过计算数据集样本中每个单词的频率。 描述单词可能比特定的数据单词更频繁地出现。您必须调整阈值以找到将单词划分为两个类别的阈值。如果这不适用于所有情况,例如,因为有经常发生的IP地址,手动黑名单应该解决这个问题。
  • 仅通过从其 description 单词集合中计算签名来处理每一行(连接的 description 单词的字符串散列应该起作用)。计算每个签名的出现次数。

性能(非常粗略估计):在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)

如果对输入没有任何假设,很难回答。

我的方法是:

  1. 查看您的输入并制定有关分组的规则,并实施这些规则(可能使用正则表达式)。
  2. 然后为那些与之匹配的人创建一个“misc”组。
  3. 收集数据,并查看该组中是否存在可以为其编写正则表达式的模式。
  4. 重复,直到你有一个令人满意的小“misc”组。
  5. 如果您想要将一组特定的“相似”字符串组合在一起,则只能告诉您(无需假设)。必须有一定数量的错误消息类型,因此在经过几次迭代后,这应该会产生足够好的结果。

    另一种方法可能是,假设相似性基于字符串中的数字和IP地址,就是搜索那些,并使用这些数字对项目进行分类。这再次涉及对您的规则更具体,但可能会让您免于迭代的规则制定(如果假设实际上有效)。

    可以考虑测量字符串之间的levenshtein距离(或使用类似的算法),但是对于需要太多步骤的分类。

答案 6 :(得分:0)

嗯,为什么重新发明轮子 - 看一下splunk的免费版本,为这类任务设计

答案 7 :(得分:0)

如果由我决定,我会使用python和正则表达式。免责声明:我从未在你所谈论的规模上使用过python。

以下是样本输出中的前两项,用正则表达式表示法重写:

  • r“客户端[。\ d]加载页面\ w + .php +因错误而失败\ d +”
  • r“连接到[。\ d] +端口\ d +超时客户端[。\ d] +源端口\ d +”

没那么糟,对吧?您可以将它们用作顶级存储桶的“密钥”。

然后,将给定字符串与存储桶匹配的代码变得非常简单:

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)

我认为需要一些手动干预,频率计数将是最好的方法。