【问题标题】:How to match character strings that are actually random?如何匹配实际上是随机的字符串?
【发布时间】:2017-02-09 05:11:07
【问题描述】:

我正在编写的 Python 应用程序需要从源代码中提取标识符和文本字符串。它发现的一小部分是(看似)随机字符串。我想过滤掉它们,但到目前为止还不能创建一个正则表达式来做到这一点。仅按长度过滤是不可能的,因为有一些非常长的标识符是有效的。这是一个随机抽取的示例,与相同长度的有效标识符相比:

UGxhemEgZGUgaWZXNaWdhZGyOiBDSUWRVNUQVYtSVBOIFVuaWQ
NSApplicationDidChangeScreenParametersNotification

有没有办法编写一个正则表达式或其他检测系统来检测这样的垃圾序列?我开始怀疑如果不对大型单词字典测试字符串就无法做到这一点,我认为这很容易出错并且是计算密集型的。但是,也许更聪明的人知道这样一种检测或匹配随机序列的方法?

这个问题的理想解决方案是一个函数,它可以将字符串作为输入,并报告它是否“可能”是随机的。它可能会产生误报(错误地将一些随机字符串误报为非随机字符串),最好概率很低,但它不能报告误报(有些东西不是随机的)。以防万一,字符串的长度似乎在 25 到 80 个字符之间。

EDIT #1 2017-02-08:进一步思考,我想到一种可能的方法可能是匹配行中最少数量的唯一字符的正则表达式。例如,第二个字符必须与第一个不同,第三个与前两个不同,第四个与前三个不同,等等。足够长的版本可能会捕获很多随机序列。但是,查看不同的正则表达式运算符,我没有看到(因为缺少更好的词)“负反向引用”或“匹配的东西 other 而不是你刚刚匹配的东西”的版本。如果有人知道这方面的变化,也许我可以让它工作。

EDIT #1 2017-02-10:我担心我编写上面两个示例字符串的方式可能会被误解为单个字符串。上面的例子是两个相同长度的独立字符串——如果不清楚,我深表歉意。这里还有一些例子;每行都是一个单独的标识符。这也是故意显示不同的长度。

shouldBeAbleToCountLiveNeighboursOfACellOnDiagonalsAndStraightLines
eXNZWzIGbHRpbWVkaWEgYWkIGFuaWhdGlvbiBkaXNcmlidXRlZCNCpUgRGlzdHJpYnV
dWxLXRvbGVyYWIHJlYWwtdGltZSBzeXNZWzLgKlSBEaXNcmlidXRlZCBBcmNoaXRlYR
dGhIExvIHNYmltbMgYSBsYSBwWdpbmEgeSBsbyBhbnVuYlhbWzIGVuIGVsIHByhpbWg
aGUgYuZmVyZWjZSBwcmjZWVkaWncygDQoNClNYmpcNpbNCkluIGyZGVyIHRvIHN
YQKUGFyYTogZXNYFyQGluYWlcCteAKQMIExaXMgQSgUGluZWRhDQpDQzogQuYVw
thehasSizeMatcherShouldMatchACollectionWithExpectedSize
QycmVvIGRlIERpcVtaWhYnDsgZGUgYWNaXZpZGFkZXMgZGUgbGEg
NSAppleEventManagerWillProcessFirstEventNotification
SNMTransformGizmoRotationControllerPerformTransform
RndkOiBEaWZcnDsgZGUgYudmjYXRvcmlhIFNVTUJVCBlbiBSRUJ

不管它有什么价值,我把我的应用程序从大约 900 个 GitHub 存储库的半随机选择中提取的 pastebin a list of the 1000 longest identifiers 放在了上面。它包含真实标识符和随机字符串。

【问题讨论】:

  • NLTK 可能对此有用。
  • 假设有效标记包含英语,无效标记将有更多的 4 个或更多连续辅音。
  • 乍一看,如果字符串长度足够大(25-80 可能还可以),那么如何计算每个字母的频率,并将这个分布与典型的英语语言进行比较。
  • 这些想法很有趣,swbandit 和 Roman Fursenko。谢谢!我将探索它们。我特别喜欢@swbandit 的想法,因为它可以表示为正则表达式。
  • @swbandit 你的想法让我成功了一半。事实证明,由于其中一些字符串是标识符,它们有时是多个单词的串联,因此不遵循典型的字母分布。但是,将连续 10 个辅音的正则表达式与最小长度限制组合在一起可以捕获所有非常长的字符串。这就是进步!谢谢。

标签: python regex random pattern-matching


【解决方案1】:

首先,谢谢你,你的问题让我很感兴趣,而且我正在寻找一些有趣的培训。下面我在你的帖子中实现了我在 cmets 中提到的想法,以及 @swbandit 的想法。也可以通过修改is_rnd 函数在代码中添加任何其他策略。 我从这里找到的短字典 (https://gist.github.com/jbergantine/2390284) 中生成了有意识的字符串(当然,这本字典很小,可能不具有代表性,但我将其用于测试目的)。这些字符串在代码中被引用为strok。之后,生成了相同长度的随机字符串(strrnd)。我只使用了小写字符并假设字符串中没有空格。

如果字符串是随机的,函数is_rnd1is_rnd2 返回True。函数is_rnd1 检查最常见的英文字符'e' (12.7%) 和最罕见的'z' (0.074%) 的频率。然而,在函数中,频率的边界被显着扩展。函数is_rnd2 只需按照@swbandit 的建议检查四个连续辅音的出现。

在代码的测试部分,上面描述的函数针对不同长度的字符串进行了测试,该字符串以构成strok的字数来衡量。函数is_rnd 被调用两次。首先是strok,其次是随机字符串。定义字符串随机与否的错误相加。

所以这是代码:

nouns = ['here is a list from gist.github.com/jbergantine/2390284']
allch = "abcdefghijklmnopqrstuvwxyz"

import numpy as np
import matplotlib.pyplot as plt
import random, string
import collections
import re

alb = 'etaoinshrdlcumwfgypbvkjxqz'

def frqlist(s):
    dic = collections.Counter(s)
    arr = np.zeros(len(alb))
    for key in dic:
        idx = alb.index(key)
        arr[idx] = float(dic[key])/float(len(s))
    return arr

def generate_strs(nw=1):
    strok = ''.join([nouns[random.randrange(0, len(nouns))] 
                     for i in range(nw)])
    rand_str = lambda n: ''.join([random.choice(string.lowercase) 
                         for i in xrange(n)])
    strrnd = rand_str(len(strok))
    return strok, strrnd

def is_rnd1(s):
    fq = frqlist(s)
    return not (fq[0] > 0.07 and fq[-1] < 0.01)

def is_rnd2(s):
    return re.search(r'[^aeiou]{4}', s)

maxwords = 12
nprobe = 1000

is_rnd = is_rnd1
nwa = []
err = []
print "Words\t% of errors"
for nw in range(1, maxwords):
    errok = 0
    errrnd = 0
    for i in range(0, nprobe):
        strok, strrnd = generate_strs(nw)
        if is_rnd(strok):
            errok += 1./nprobe
        if not is_rnd(strrnd):
            errrnd += 1./nprobe
    print nw, "\t", (errok*100. + errrnd*100.)/2.
    nwa.append(nw)
    err.append((errok*100. + errrnd*100.)/2.)

plt.plot(nwa, err)
plt.show()

以下是一些结果

For function is_rnd1
Words   % of errors
1   28.2
2   20.45
3   17.15
4   13.15
5   13.7
6   10.65
7   9.25
8   7.35
9   6.5

For function is_rnd2 (4 consecutive consonants)
Words   % of errors
1   23.15
2   13.0
3   13.55
4   17.75
5   22.2
6   24.35
7   27.7
8   30.6
9   33.25

For function is_rnd2 (6 consecutive consonants)
Words   % of errors
1   39.45
2   20.8
3   11.9
4   6.5
5   4.05
6   3.05
7   2.5
8   1.6
9   2.0

对我来说,结果很有趣。

更新:

我尝试过机器学习。我使用了一个有 26 个入口和 1 个输出的神经元。在入口处提供字符串中字符的频率。如果字符串是随机的,则输出为 1,否则为 0。神经元由以下类描述:

class Neuron(object):
    def __init__(self, nin, wt=0.):
        self.nin = nin
        self.w = np.full(nin, wt, dtype=np.float32)
        self.out = 0.
        self.learnspd = 0.01

    def result(self, ins):
        self.out = np.sum(self.w * ins)
        self.out = 1. if self.out > 0.1 else 0.
        return self.out

    def correctw(self, ins, err):
        self.w = self.w + err*self.learnspd*ins

定义神经元neuron = Neuron(len(alb))后,实现其学习过程:

def learning(neuron, nset, maxwords):
    for i in xrange(nset):
        nw = np.random.randint(1, maxwords+1)
        strok, strrnd = generate_strs(nw)
        fq = frqlist(strok)
        neurres = neuron.result(fq)
        errok = 0.0 - neurres
        neuron.correctw(fq, errok)
        fq = frqlist(strrnd)
        neurres = neuron.result(fq)
        errrnd = 1.0 - neurres
        neuron.correctw(fq, errrnd)

让我们学习learning(neuron, nset, maxwords)

终于可以使用神经元了:

def is_rnd_neuron(s, neuron):
    fq = frqlist(s)
    return bool(neuron.result(fq))

使用与上述相同的测试程序我得到了以下结果:

nset = 100
Words   % of errors
1   50.0
2   50.0
3   50.0
4   50.0
5   50.0
6   50.0
7   50.0
8   50.0
9   50.0
nset = 500
Words   % of errors
1   20.4
2   13.25
3   9.5
4   5.55
5   5.95
6   3.9
7   3.35
8   2.55
9   2.4
nset = 1000
Words   % of errors
1   16.95
2   9.35
3   4.55
4   2.4
5   1.7
6   0.65
7   0.4
8   0.15
9   0.1
nset = 5000
Words   % of errors
1   16.6
2   7.25
3   3.8
4   1.8
5   1.1
6   0.5
7   0.1
8   0.1
9   0.05

我真的很佩服它是多么容易实现以及它产生的结果多么好。

【讨论】:

  • @roman-fursenko 这真的很酷:-)。我用一个指向从 github 存储库中随机选择的 1000 个真实字符串的 pastebin 的指针更新了这个问题。我计划尽快尝试您的代码。感谢您的详细努力。
【解决方案2】:

您可以查看依赖于马尔可夫链的 rrenaud's gibberish detector。您需要更改给定的代码以满足您的需要,因为您将寻找包含非乱码的乱码。但它可能会有所帮助。

这可能是一个很好的起点。

但是…… 我首先尝试自己解决这个问题,这让我很开心。这可能不是最好的答案,它完全依赖于以大写字母开头的每个单词(基于您给定的测试输入)。但是,我喜欢玩弄一些想法来获得一个很好的结果,但是在更长的文本(你可能会看到的那种)上会非常慢。

import enchant #Spellchecker
import re

endict = enchant.Dict("en_US")

#Test input...
instr = 'UGxhemEgZGUgaWZXNaWdhZGyOiBDSUWRVNUQVYtSVBOIFVuaWQNSApplicationDidChangeScreenP arametersNotification'
outstr = ''

acceptable_short_words = ['a', 'I', 'if', 'is'] #Any one or two letter words you can think of that are OK.

#Split the words based on capitals.
words = re.findall('[A-Z][^A-Z]*', instr)

def isWord(wordin):
    if(wordin in acceptable_short_words):
        return True
    elif(len(wordin) < 3):
        return False

    return endict.check(wordin)

for w in words:
    if(isWord(w)):
        outstr += " " + w

print(outstr.strip())

运行此脚本会导致:

I Application Did Change Screen Parameters Notification

起初我尝试在字符串中搜索单词。结果很差,因为它检测到单词中的单词(例如:通知还包含单词“Not”、“if”、“cat”、“at”等) 所以我决定拆分大写字母,并根据字典检查每个元素。这也不好用,因为许多单字母单词被证明是英文单词:

U - 形容词|英式|非正式的:(语言或社会行为的)上层社会阶层的特征或适合的。 (“U 礼仪”)

谁知道?

所以最后,我决定忽略任何我不知道的短词(结果很多)并将其限制为常见的短词。 我可以更进一步,使用 NLTK 或类似工具以类似于乱码检测器的方式检查词对频率。但是已经做了很多次了,我宁愿不这样做。

【讨论】:

  • 谢谢。阅读上面的示例,我不确定我的问题的措辞是否清楚地表明我给出的示例包含 2 个单独的字符串,而不是一个跨行的字符串。如果不清楚,我真的很抱歉。我已经对问题添加了一些澄清性的编辑,还添加了一个指向包含更多示例的 pastebin 的指针。
猜你喜欢
  • 2015-11-06
  • 2011-09-16
  • 2016-07-19
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多