即,给出一个str元组列表:
- [('t','h','i','s','\ue000'),('c','o','r','p','u',('i','n',('t','x','t','f','l','e',('s','c',('b','a',('a','d',('f',('.','\ue000')]
和一个字符串元组:(‘我’,’s’)
如何处理列表,使其迭代所有元组键,并用(‘is’)替换(‘i’,’s’),即输出Counter看起来像这样:
- [('t','is',('is','\ue000')]
我试过这个:
- >>> cin
- [('t','\ue000')]
- >>> [tuple(' '.join(i).replace(' '.join(qtuple),''.join(qtuple)).split()) for i in cin]
- [('t','\ue000')]
但是有没有比循环遍历每个单词更有效的方法,然后将它们更改为字符串以进行替换并再次拆分它们然后将它们转换回元组?
正则表达式替换会更快吗?有没有办法处理元组列表而不处理字符串?
我试过这个,似乎用str.replace替换字符串不是问题.它真的在计算双子座并提取它们:
- import io
- from collections import Counter
- import time
- infile = 'big.txt' # comes from norvig.com/big.txt
- n = 2
- with io.open(infile,encoding='utf8') as fin:
- text = fin.read().lower().replace(u' ',u"\uE000")
- for j in range(1,6400):
- unused_char = unichr(ord(u'\uE001') + j)
- start = time.time()
- char_bigrams = zip(*[text[i:] for i in range(n)])
- bigram_time = time.time() - start
- start = time.time()
- most_freq_bigram = Counter(filter(lambda x: u"\uE000" not in x and '\n' not in x,char_bigrams)).most_common(1)[0][0]
- max_time = time.time() - start
- start = time.time()
- text = text.replace(''.join(most_freq_bigram),unused_char)
- replace_time = time.time() - start
- print j,''.join(most_freq_bigram),most_freq_bigram,bigram_time,max_time,replace_time
- print text
这是在norvig.com/big.txt测试的
[OUT]:
- 1 th (u't',u'h') 0.896255016327 3.28389787674 0.0253069400787
- 2 e (u'\ue002',u'e') 1.47053217888 3.16544914246 0.0280749797821
- 3 in (u'i',u'n') 1.13404297829 3.10529899597 0.0245559215546
- 4 an (u'a',u'n') 1.20013689995 3.63801002502 0.0242891311646
- 5 er (u'e',u'r') 1.41387891769 3.13376092911 0.0237591266632
- 6 on (u'o',u'n') 1.22826981544 3.06997895241 0.0227301120758
- 7 re (u'r',u'e') 1.21916294098 2.97599196434 0.0238041877747
- 8 at (u'a',u't') 1.14608097076 2.97988891602 0.0226521492004
- 9 en (u'e',u'n') 1.20747494698 2.88649988174 0.019054889679
- 10 ed (u'e',u'd') 1.16296696663 2.8995718956 0.0198271274567
- 11 is (u'i',u's') 1.17692494392 3.02292394638 0.0228500366211
- 12 d (u'\ue005',u'd') 1.13779211044 2.85169506073 0.0229239463806
我已经尝试过scikit-learn CountVectorizer,我似乎没有使用zip那么快,参见Fast/Optimize N-gram implementations in python
另外,如果没有它们在Counter步骤中进行过滤操作,则需要更长的时间.计数器操作每次迭代需要3秒=(
如何优化此操作?
- Counter(filter(lambda x: u"\uE000" not in x and '\n' not in x,char_bigrams)).most_common(1)[0][0]
解决方法
- def cons_2(word_list,t):
- j = ''.join(t)
- f = lambda acc,e: acc[:-1] + (j,) if (acc[-1] == t[0] and e == t[1]) else acc + (e,)
- return [reduce(f,i[1:],(i[0],)) for i in word_list]
- print cons_2(cin,'s'))
不涉及替换,f应用于每个元素i,cin的值不会改变,而是生成并返回新的数组.
细节:
> reduce对每个数组元素i应用f并将值返回到累加器acc.
>减少参数:
> f:要应用的功能.
> i [1:]:数组迭代所有元素但第一个.
>(i [0],):累加器的初始值,它是一个元组,带有输入元组i的第一个值.
> f:是一个lambda函数,累加器acc和当前元素e作为输入:
>如果累加器的最后一个元素等于字符串元组的第一个元素,并且当前元素e等于字符串元组的第二个元素,则返回元组:acc [-1](j,)else继续正常连接:acc(e,).
对于字符串元组> 2这个想法是一样的,但我们必须管理元组的长度l.
- def cons_n(word_list,t):
- l = len(t)
- j = ''.join(t)
- f = lambda acc,e: acc[:-l] + (j,e,) if acc[-l:] == t or acc[:l] == t else acc + (e,i[l:],(i[:l])) for i in word_list]
- print cons_n(cin,'s'))
这应该适用于n长度的字符串元组.
细节:
>与上面相同的过程,但使用l:reduce将f应用于其余元素i [l:],并且累加器的初始值是具有前l个元素的元组:(i [:l]).
>向后和向前检查l元素是否等于字符串元组t,如果为true,则添加元组:acc [: – l](j,).
这是一种功能方法,没有数据被修改但是生成,因此同时拥有多个进程应该是安全的(理论上,我不是Python解释器的专家).
如果上面的代码对于没有进入函数式编程的人来说太奇怪了,这是另一种方法:
- def cons_n_iter(tuple_list,tuple_seq):
- jnt = ''.join(tuple_seq)
- lnt = len(tuple_seq)
- res = []
- for word in tuple_list:
- acc = (word[:lnt])
- for letter in word[lnt:]:
- if acc[-lnt:] == tuple_seq or acc[:lnt] == tuple_seq:
- acc = acc[:-lnt] + (jnt,letter,)
- else:
- acc += (letter,)
- res += (acc,)
- return res
- print cons_n_iter(cin,'s'))