14

我正在尝试通过 Timus Online Judge 解决这个问题。要解决这个问题,您需要生成一个由 1 000 000 个小写拉丁字母组成的序列,并在 1 秒内将其写入标准输入。

用 C++ 或 Java 很容易解决这个问题。我在这里有python解决方案:

import os
from random import randint

s = ''.join(chr(97 + randint(0, 25)) for i in range(1000000))
os.write(1, bytes(s, 'utf8'))

耗时 1.7 秒:

$ time python3.3 1219.py > /dev/null

real    0m1.756s
user    0m1.744s
sys     0m0.008s

结果是“超出时间限制”。所以问题是“如何更快地做到这一点?”

UPD1:使用randint(97, 122)减少了 16ms 的时间。现在是 1.740s

UPD2: @Martijn Pieters 的解决方案需要 0.979 秒,但它也没有通过测试。

UPD3 Martijn Pieters提出了一个非常好的解决方案,但仍然很慢:

from sys import stdin
from random import choice
from string import ascii_lowercase

s = ''.join([choice(ascii_lowercase) for _ in range(1000000)])
stdout.write(s) 

耗时0.924s

from sys import stdout
from random import choice
from string import ascii_lowercase

for _ in range(1000000):
    stdout.write(choice(ascii_lowercase))

耗时1.173s

from sys import stdout
from random import choice
from string import ascii_lowercase
bal = [c.encode('ascii') for c in ascii_lowercase]
out = stdout.buffer

for _ in range(1000000):
    out.write(choice(bal))

耗时1.155 秒

from sys import stdout
from random import choice
from string import ascii_lowercase

bal = [c.encode('ascii') for c in ascii_lowercase]
stdout.buffer.write(b''.join([choice(bal) for _ in range(1000000)]))

耗时0.901 秒

UPD4

有人刚刚解决了 Timus 的问题。我希望他能分享他的解决方案:)

UPD5 感谢Ashwini Chaudhary与我们分享他的 Python 2.x 解决方案:

from random import choice
from string import ascii_lowercase
lis=list(ascii_lowercase)
print ''.join(choice(lis) for _ in xrange(1000000)) 

在我的电脑上它需要0.527 秒,它通过了Timus的测试。但是 Python3.x 的问题仍然存在。

UPD6 感谢Markku K。这段代码:

import os
from random import random
from string import ascii_lowercase

bal = [c.encode('ascii') for c in ascii_lowercase]
os.write(1, b''.join([bal[int(random() * 26)] for _ in range(1000000)]))

耗时 0.445s,但仍未通过测试

4

9 回答 9

9

这是在几秒钟内生成 1000000 个“随机”小写字母的 Python 3 代码0.28(另请参见0.11末尾的 -seconds 解决方案;来自问题的@Ashwini Chaudhary 的代码0.55在我的机器上需要几秒钟,@Markku K. 的代码 -- 0.53):

#!/usr/bin/env python3
import os
import sys

def write_random_lowercase(n):
    min_lc = ord(b'a')
    len_lc = 26
    ba = bytearray(os.urandom(n))
    for i, b in enumerate(ba):
        ba[i] = min_lc + b % len_lc # convert 0..255 to 97..122
    sys.stdout.buffer.write(ba)

write_random_lowercase(1000000)

% len_lc虽然它仍然满足条件(ascii、小写、1、2、3 个字母序列的频率),但会扭曲分布(参见最后如何修复它):

$ python3 generate-random.py | python3 check-seq.py

其中check-seq.py

#!/usr/bin/env python3
import sys
from collections import Counter
from string import ascii_lowercase

def main():
    limits = [40000, 2000, 100]

    s = sys.stdin.buffer.readline() # a single line
    assert 1000000 <= len(s) <= 1000002 # check length +/- newline
    s.decode('ascii','strict') # check ascii
    assert set(s) == set(ascii_lowercase.encode('ascii')) # check lowercase

    for n, lim in enumerate(limits, start=1):
        freq = Counter(tuple(s[i:i+n]) for i in range(len(s)))
        assert max(freq.values()) <= lim, freq

main()

注意:在 acm.timus.ru 上generate-random.py给出“超出输出限制”。

为了提高性能,您可以使用bytes.translate()方法0.11秒):

#!/usr/bin/env python3
import os
import sys

# make translation table from 0..255 to 97..122
tbl = bytes.maketrans(bytearray(range(256)),
                      bytearray([ord(b'a') + b % 26 for b in range(256)]))
# generate random bytes and translate them to lowercase ascii
sys.stdout.buffer.write(os.urandom(1000000).translate(tbl))

如何修复% len_lc歪斜

25626(字节数)不能被(小拉丁字母的数量)整除,因此该公式min_lc + b % len_lc使某些值的出现频率低于其他值,例如:

#!/usr/bin/env python3
"""Find out skew: x = 97 + y % 26 where y is uniform from [0, 256) range."""
from collections import Counter, defaultdict

def find_skew(random_bytes):
    char2freq = Counter(chr(ord(b'a') + b % 26) for b in random_bytes)
    freq2char = defaultdict(set)
    for char, freq in char2freq.items():
        freq2char[freq].add(char)
    return {f: ''.join(sorted(c)) for f, c in freq2char.items()}

print(find_skew(range(256)))
# -> {9: 'wxyz', 10: 'abcdefghijklmnopqrstuv'}

在这里,输入range(256)是均匀分布的(每个字节只出现一次),但输出中的字母比'wxyz'其余字母的出现频率要低。要修复它,可以删除未对齐的字节:910

print(find_skew(range(256 - (256 % 26))))
# -> {9: 'abcdefghijklmnopqrstuvwxyz'}

这里,输入是均匀分布的字节范围内[0, 234)的输出是均匀分布的ascii小写字母。

bytes.translate()接受第二个参数来指定要删除的字节:

#!/usr/bin/env python3
import os
import sys

nbytes = 256
nletters = 26
naligned = nbytes - (nbytes % nletters)
tbl = bytes.maketrans(bytearray(range(naligned)),
                      bytearray([ord(b'a') + b % nletters
                                 for b in range(naligned)]))
bytes2delete = bytearray(range(naligned, nbytes))
R = lambda n: os.urandom(n).translate(tbl, bytes2delete)

def write_random_ascii_lowercase_letters(write, n):
    """*write* *n* random ascii lowercase letters."""    
    while n > 0:
        # R(n) expected to drop `(nbytes - nletters) / nbytes` bytes
        # to compensate, increase the initial size        
        n -= write(memoryview(R(n * nbytes // naligned + 1))[:n])

write = sys.stdout.buffer.write
write_random_ascii_lowercase_letters(write, 1000000)

如果随机生成器(此处)生成超出对齐范围( )的os.urandom长字节序列,则循环可能会执行多次。>=234while

random.getrandbits(8*n).to_bytes(n, 'big')如果使用 代替 ,则时间性能可以提高另一个数量级os.urandom(n)。前者使用 Mersenne Twister 作为核心生成器,可能比os.urandom()使用操作系统提供的源更快。如果您使用随机字符串作为秘密,则后者更安全。

于 2013-04-30T23:49:00.050 回答
5

使用string.ascii_lowercase而不是chr生成小写字符:

from sys import stdin
from random import choice
from string import ascii_lowercase

s = ''.join([choice(ascii_lowercase) for _ in range(1000000)])
stdout.write(s)

同样直接写入stdout似乎更快,在python中编码自己并不比在C代码中处理它更快。

我也使用列表理解;str.join()需要扫描输入序列两次,一次确定输出的长度,一次实际将输入元素复制到输出字符串。然后,列表推导击败了较慢的生成器到列表代码。

仅使用choice(ascii_lowercase)从整数生成每个字符的方法就快两倍多:

>>> timeit.timeit('f()', 'from __main__ import yours as f', number=3)
11.299837955011753
>>> timeit.timeit('f()', 'from __main__ import mine as f', number=3)
5.330044150992762

''.join()您可以尝试通过将单个字符直接写入来避免开销stdout

from sys import stdout
from random import choice
from string import ascii_lowercase

for _ in range(1000000):
    stdout.write(choice(ascii_lowercase))

接下来要尝试的是写入原始字节:

from sys import stdout
from random import choice
from string import ascii_lowercase
bal = [c.encode('ascii') for c in ascii_lowercase]
out = stdout.buffer

for _ in range(1000000):
    out.write(choice(bal))

''.join()但这些在我的测试中没有任何改进。

接下来我们将 ASCII 字符编码为字节一次,然后使用bytes.join()

from sys import stdout
from random import choice
from string import ascii_lowercase

bal = [c.encode('ascii') for c in ascii_lowercase]
stdout.buffer.write(b''.join([choice(bal) for _ in range(1000000)]))

bal是编码为字节的小写 ASCII 字符列表,我们从中随机挑选 100 万个项目,将它们连接成一个大字节字符串,然后将其一次性写入二进制标准输出缓冲区。

字节连接与字符串版本一样“慢”:

>>> timeit.timeit('f()', 'from __main__ import bytes as f', number=3)
5.41390264898655

但我们编码 26 个字符,而不是 100 万个,因此写入阶段更快。

于 2013-04-30T21:20:26.287 回答
2

我刚刚被接受的解决方案(python 2.7,执行时间:0.984):

from random import choice
from string import ascii_lowercase

lis = list(ascii_lowercase)
print ''.join(choice(lis) for _ in xrange(1000000)) 

访问列表元素比访问字符串更快。

In [13]: from random import choice

In [14]: from string import ascii_lowercase

In [15]: lis = list(ascii_lowercase)

In [16]: %timeit ''.join(choice(lis) for _ in xrange(10**5))
1 loops, best of 3: 128 ms per loop

In [17]: %timeit ''.join(choice(ascii_lowercase) for _ in xrange(10**5))
1 loops, best of 3: 134 ms per loop

而且您不需要stdoutstdin在这里,因为大多数在线判断我们这样的东西来测试您的脚本:

$python script.py <in.txt >out.txt

因此,您可以使用print代替stdoutraw_input()代替stdin,尽管对于大量输入stdin.readline来说比raw_input().

更新 1

在 py2.7 中使用 @Markku的 提示执行时间减少到 0.64:

from random import random
from string import ascii_lowercase

lis = list(ascii_lowercase)
print "".join( [lis[int(random() * 26)] for _ in xrange(1000000)] )
于 2013-04-30T22:03:22.860 回答
2

通过在原始解决方案中从 randint(0,25) 更改为 int(random()*25) ,我获得了巨大的速度提升。在我的机器上,时间从大约 2 秒变为大约 0.6 秒。如果您查看 random.py 代码,您会发现 randint 充满了您不想要或不需要的检查。

更新:糟糕,减一。你需要 int(random()*26)。谢谢阿什维尼_

于 2013-04-30T22:41:01.020 回答
1

尝试将其中的一部分转换为 C++ 或其他编译语言。这几乎可以保证让它更快。不幸的是,Python 并不太快,尤其是在涉及到这样的事情时。尝试 C++、C 或Pascal

编辑:另见Python 性能提示

于 2013-04-30T21:11:40.977 回答
1

使用随机选择

在 Python 3.6 上:

随机导入
导入字符串

%timeit ''.join(random.choices(string.ascii_lowercase, k=10**6))
1 个循环,最好的 3 个:每个循环 235 毫秒
于 2017-06-01T15:54:51.440 回答
0

生成和写入大小为 2 的较大幂的块。

也许使用 26 个小写字母的字符串或数组,然后随机选择而不是生成字符。

于 2013-04-30T21:10:31.120 回答
0

执行时间 0.51s

from sys import stdout
from string import ascii_lowercase
l = 1000000
q = ['a']*l
lc = list(ascii_lowercase)
c = 0
for i in range(0,l-2,3):
    j = i // 3
    j_26 = j // 26
    q[i]= lc[j_26 // 26 % 26]
    q[i+1] = lc[j_26 % 26]
    q[i+2] = lc[j % 26]

stdout.write(''.join(q))
于 2020-05-26T14:41:59.403 回答
-2

也许:

import _random

x = _random.Random()
for y in range( 1000000 ): 
  a = x.random()
于 2021-04-23T17:23:29.843 回答