Python 中的高效列表构建

Efficient list building in Python

作为 Matlab 的长期用户,每当我在一个循环中构建一个包含多个元素的 list/array/anything 时,我习惯于得到警告,这样它每次都会改变大小,因为这会减慢速度。

当我自学 Python 2.7 时,我想知道这样的规则是否适用于字符串。我确切地知道我想要我的字符串有多长,并且我有一个我想要构建它的字符的特定列表,但除此之外我希望它是随机的。到目前为止,我最喜欢的代码是:

def BernSeq(length,freq):
"""Create a Bernoulli sequence - a random bitstring - of the given length 
and with the given frequency of 1s"""
    seq = '0'*length
    for ii in range(length):
        num = np.random.rand(1)
        if num < freq:
            cha = '1'
            seq = seq[:ii] + cha + seq[ii+1:]

我将其称为 BernSeq(20,.25) 并得到输出 '10001000000001011101'.

我已经尝试过 seq[ii] = '1',但是,用 IPython 的话来说,TypeError: 'str' object does not support item assignment

那么,我是不是用最 Pythonic 的方式来做这件事,还是有一些我还没有见过的花招 - 也许是一个随机字符串或列表生成器,我可以直接给它一个我希望它随机选择的字符列表,我希望每种可能性都有的概率,以及我希望这个字符串或列表有多长?

(我看过 other questions 关于随机字符串的内容,但虽然他们确实解决了如何使其长度正确的问题,但他们通常会尝试生成密码,所有 ASCII 字符的可能性均等。这不是我想要什么。)

有几种方法。

首先,您可以从一开始就创建正确的元素:

seq = "".join("1" if np.random.rand(1) < freq else "0" for _ in range(length))

但要问的第一个问题是:您想要什么作为输出?你要求它是一个字符串吗?也许您可以接受布尔值列表?

然后

seq = [np.random.rand(1) < freq for _ in range(length)]

也够了

您可以根据您的分布偏好定义一个生成随机字母的函数,然后按您喜欢的次数执行它。

import random
def my_letter():
   a = random.randint(1,10)
   if a > 5:
   return "a"
   else:
   return "b"

那么对于您的长度偏好:

my_str = ""
for x in range(length):
   my_str += my_letter()

python 中的字符串是不可变的

In [1]: a = '1' * 5

In [2]: a
Out[2]: '11111'

In [3]: type(a)
Out[3]: str

In [4]: a[2] = 'c'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-69ed835eb212> in <module>()
----> 1 a[2] = 'c'

TypeError: 'str' object does not support item assignment

In [5]: b = [1] * 5

In [6]: b
Out[6]: [1, 1, 1, 1, 1]

调整您的代码以使用 int 列表。 (最少的修复,不修复样式或优化其他东西)

def BernSeq(length,freq):
"""Create a Bernoulli sequence - a random bitstring - of the given length 
and with the given frequency of 1s"""
    seq = [0] * length
    for ii in range(length):
        num = np.random.rand(1)
        if num < freq:
            cha = 1
            seq.append(cha)

NumPy 中存在二项分布,np.random.binomial。以您想要的频率从中采样,然后将字符串表示形式连接在一起,这比您自己重新发明它要快。

def bernouilli_str(N, one_freq):
    return ''.join(np.random.binomial(1, one_freq, N).astype('U1'))

基准

In [112]: %timeit ''.join(np.random.binomial(1, 0.75, 10**6).astype('U1'))
637 ms ± 5.75 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [113]: %timeit "".join("1" if np.random.rand(1) < 0.75 else "0" for _ in range(10**6))
1.69 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)