我正试图解决Timus Online Judge的this问题。要解决此问题,您需要生成一个1 000 000个小写拉丁字母的序列,并在1秒内将其写入stdin。
使用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)
会减少16毫秒的时间。现在是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.155s
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.901s
UPD4
蒂姆斯有些人just solved有问题。我希望他能分享他的解决方案:)
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.527s ,它会通过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 ,但仍未通过测试
答案 0 :(得分:8)
这是Python 3代码,在0.28
秒内生成1000000个“随机”小写字母(另请参阅0.11
- 秒结尾的解决方案; @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()
method(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
倾斜 256
(字节数)不能被26
(低位拉丁字母的数量)整除,因此公式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'
个字母比其余9
与10
的频率相同发生。要修复它,可以删除未对齐的字节:
print(find_skew(range(256 - (256 % 26))))
# -> {9: 'abcdefghijklmnopqrstuvwxyz'}
这里,输入是[0, 234)
范围内均匀分布的字节,输出均匀分布为小写字母。
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
)生成超出对齐范围(>=234
)的长字节序列,则while
循环可执行多次。
如果使用random.getrandbits(8*n).to_bytes(n, 'big')
代替os.urandom(n)
,则时间性能可以提高一个数量级。前者使用Mersenne Twister作为核心生成器,可能比使用操作系统提供的源的os.urandom()
更快。如果你使用随机字符串作为秘密,后者会更安全。
答案 1 :(得分:4)
使用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万个,因此写入阶段更快。
答案 2 :(得分: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
你在这里不需要stdout
或stdin
,因为大多数在线评判我们这样的东西来测试你的剧本:
$python script.py <in.txt >out.txt
因此,您可以使用print
代替stdout
和raw_input()
代替stdin
,但对于大量输入stdin.readline
要比raw_input()
快
更新1 :
在py2.7中使用@Markku的tip执行时间减少到.64:
from random import random
from string import ascii_lowercase
lis = list(ascii_lowercase)
print "".join( [lis[int(random() * 26)] for _ in xrange(1000000)] )
答案 3 :(得分:2)
通过在原始解决方案中从randint(0,25)更改为int(random()* 25),我获得了巨大的速度提升。在我的机器上,时间从大约2秒到大约0.6秒。如果你看一下random.py代码,你会发现randint里面装满了你不想要或不需要的支票。
更新:哎呀,一个接一个。你需要int(random()* 26)。谢谢Ashwini
答案 4 :(得分:1)
尝试将其中的某些部分转换为C ++或其他编译语言。这几乎可以保证让它更快。不幸的是,Python并不是太快,尤其是涉及到这样的事情时。试试C ++,C或Pascal。
编辑:另请参阅Python Performance Tips
答案 5 :(得分:0)
生成并写入大小为2的较大幂的块。
也许使用26个小写字母的字符串或数组然后随机选择而不是生成字符。
答案 6 :(得分:0)
在Python 3.6上:
import random import string %timeit ''.join(random.choices(string.ascii_lowercase, k=10**6)) 1 loop, best of 3: 235 ms per loop
答案 7 :(得分: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))
答案 8 :(得分:-2)
也许:
import _random
x = _random.Random()
for y in range( 1000000 ):
a = x.random()