需要帮助搞清楚zip(),* [...]和.update()的工作原理

时间:2014-04-09 06:32:29

标签: python

程序应该读取文件然后输出N-gram的频率。在做了一些研究后,我已经找到了大部分代码。我不理解的唯一部分是:combination= (zip(*[words[i:] for i in range(n)]))c.update(combination)。 zip函数我知道它返回一个元组列表,但我不明白为什么它的参数中有for循环。

from collections import Counter
filename = r'/Users/ma/desktop/dd.txt'
textfile = open(filename, 'r')

c = Counter()

def n_grams(n):
  for line in textfile:
       words = line.split()

       combination= (zip(*[words[i:] for i in range(n)]))

       c.update(combination)
  return c

n = int(raw_input('Enter the sequence of words.'))
m= n_grams(n)

2 个答案:

答案 0 :(得分:1)

zip所做的是它在lists的新list中将两个tuples加在一起。
直接从zip文档中获取:

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zipped = zip(x, y)
>>> zipped
[(1, 4), (2, 5), (3, 6)]

*[words[i:] for i in range(n)]的结果对你来说有点难以想象,因为我不知道实际数据是什么,我只知道它可能包含的内容:

这是对代码的过度简化,使其更具可读性:

for line in textfile:
    words = line.split() # Splits on each <space>: 'my mom' will be ['my', 'mom']
    words_to_work_with = []
    for i in range(3):
        words_to_work_with.append(words[i:])

    combination=zip(*words_to_work_with)
    c.update(combination)

循环遍历文本文件中的每一行,将行拆分为多个(在 SPACE 上拆分)。
然后我们在该行上使用带有起始偏移量的单词并将其添加到如下列表中:

words_to_work_with = []
row = ['your', 'car', 'is', 'cooler', 'than', 'mine']
words_to_work_with.append(row[0:])
words_to_work_with.append(row[1:])
words_to_work_with.append(row[2:])

words_to_work_with == [('your', 'car', 'is', 'cooler', 'than', 'mine'), ('car', 'is', 'cooler', 'than', 'mine'), ('is', 'cooler', 'than', 'mine')]

最后一部分的作用是,它通过在list前加words_to_work_with来展开*。这基本上转化为:

zip(('your', 'car', 'is', 'cooler', 'than', 'mine'), ('car', 'is', 'cooler', 'than', 'mine'), ('is', 'cooler', 'than', 'mine'))

而不是:

zip([('your', 'car', 'is', 'cooler', 'than', 'mine'), ('car', 'is', 'cooler', 'than', 'mine'), ('is', 'cooler', 'than', 'mine')])

注意区别?第一个场景我们传递3个参数,在第二个场景中我们只发送一个大列表作为我们的参数。 Zip需要多个列表才能加入。 结果将是一个新列表,每个单词的一个实例只有一个正确的顺序,我猜...看起来像:

>>> list(zip(('your', 'car', 'is', 'cooler', 'than', 'mine'), ('car', 'is', 'cooler', 'than', 'mine'), ('is', 'cooler', 'than', 'mine')))
[('your', 'car', 'is'), ('car', 'is', 'cooler'), ('is', 'cooler', 'than'), ('cooler', 'than', 'mine')]

答案 1 :(得分:0)

您展示的代码计算&#34; n-gram&#34;这是您文本中n个相邻单词的序列。例如,如果文字为"A B C D E"而您正在查看3克,则您需要每次计算("A", "B", "C")("B", "C", "D")("C", "D", "E")。< / p>

它的做法有点棘手,它可能有一些错误。

关键部分是这一行:

combination= (zip(*[words[i:] for i in range(n)]))

让我们从内到外解决它。

内部部分是列表理解:[words[i:] for i in range(n)]

理解创建n列表的words个切片,每个切片都跳过比前一个更多的单词。第一个值是完整单词列表,第二个值跳过第一个单词,第三个值跳过两个单词,依此类推(最多跳过n-1个值)。

下一部分是对zip的调用,它在语法中使用*将上面创建的列表解包为单独的参数。像func(*some_list)这样的函数调用等同于func(some_list[0], some_list[1], ...)(其中...表示&#34;等等其他列表项&#34;)。它是调用具有未知数量参数的函数的有用语法。

那么,zip调用有什么作用? zip接受任意数量的可迭代参数,并且并行迭代它们,从每个参数中取一个项目并将它们打包成元组,然后继续下一个。

在这种特定情况下,迭代是所有相同单词列表的切片,因此您最终会得到列表中的单词元组。因为每个列表都与前一个列相互偏移,所以最终会出现按顺序出现的单词。这些是你正在寻找的n-gram!

zip电话之外还有一组额外的括号,它们实际上并没有做任何事情。你应该删除那些。

无论如何,这种算法在获得n-gram方面有点复杂,尽管它具有一定的优雅性。更直接的方法是处理索引并直接切出n-gram:

ngrams = [tuple(words[i:i+n]) for i in range(len(words)-n+1)]

我不知道这会更快或更慢,但对我来说它似乎更明显,所以你可能更喜欢它。

无论如何,你问到的最后一件事就是对update的调用,n-grams的列表被传递给了c。这是collections.Counter的一种方法,update实例,由于某种原因,它是一个全局变量。 Counter将提供的n-gram添加到c,顾名思义,它将对它们进行计数。如果您的文字中有重复的n-gram,则每个n-gram的计数将在c中累加。

然而,c是全球性的,有点儿麻烦。如果您想稍后计算其他文字,那么您将失去运气,因为c已经包含了之前文本的计数(因此您将获得两个文本)。实际上,现在我看一下,你也将你的文件对象作为全局变量。

您应该在函数中创建def n_grams(data, n): # pass the file (or some other iterable) as the data argument c = Counter() # create the Counter in the function for line in data: ngrams = zip(*[words[i:] for i in range(n)]) # compute the line's n-grams # or equivalently: [tuple(words[i:i+n]) for i in range(len(words)-n+1)] c.update(ngrams) # add them to the count return c # return the count at the end 并将文件对象作为参数传递给函数,因此可以根据需要在不同的数据上重用它:

{{1}}