Самый быстрый способ генерации большой случайной строки с нижними латинскими буквами
Я пытаюсь решить этой проблема от судьи Timus Online. Для решения этой задачи необходимо сгенерировать последовательность из 1 000 000 строчных латинских букв и написать его в stdin в 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 s:
$ time python3.3 1219.py > /dev/null
real 0m1.756s
user 0m1.744s
sys 0m0.008s
и я получил "превышение срока" в результате. Поэтому вопрос в том, " как это сделать быстрее?"
UPD1:
Используя randint(97, 122)
уменьшает время на 16ms. Теперь это 1.740 s
UPD2: Решение @Martijn Pieters занимает 0.979 s, но оно также не проходит тест.
UPD3 Martijn Питерс предложил очень хорошие решения, но это все еще медленно:
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.924 s
from sys import stdout
from random import choice
from string import ascii_lowercase
for _ in range(1000000):
stdout.write(choice(ascii_lowercase))
принимает 1.173 s
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 s
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 Спасибо Ашвини Чаудхари для совместного использования его 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 Спасибо К. Маркку этот код:
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.445 s, но все равно не пройти тест
7 ответов
вот код Python 3, который генерирует 1000000 "случайных" строчных букв в 0.28
секунд (см. Также 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()
метод (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)
на выходе равномерно распределены строчные буквы 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
здесь) создает длинные последовательности байтов, которые находятся вне выровненного диапазона (>=234
), то while
цикл может выполняться много раз.
производительность времени может быть улучшена на другой порядок величины, если random.getrandbits(8*n).to_bytes(n, 'big')
вместо os.urandom(n)
. Первый использует Mersenne Twister в качестве основного генератора, который может быть быстрее, чем os.urandom()
который использует предоставленные источники операционной системой. Последний более безопасен, если вы используете случайную строку для секретов.
использовать 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, закодированных в байты, из которых мы случайным образом выбираем 1 миллион элементов, объединяем их в большую байтовую строку, а затем записываем это за один раз в двоичный буфер stdout.
соединение байтов так же "медленно", как и строковая версия:
>>> timeit.timeit('f()', 'from __main__ import bytes as f', number=3)
5.41390264898655
но мы кодируем 26 символов, а не 1 миллион, поэтому этап записи быстрее.
Я получаю огромное улучшение скорости, меняя randint(0,25) на int (random ()*25) в вашем исходном решении. На моей машине время шло от примерно 2 секунд до примерно 0,6 секунды. Если вы посмотрите на random.py код, вы увидите, что randint полон проверок, которые вам не нужны или не нужны.
обновление: Упс, один. Вам нужен int (random ()*26). Спасибо Ашвини
мое решение, которое только что было принято (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:
С помощью @Маркку это совет время выполнения было сокращено до .64 в py2.7:
from random import random
from string import ascii_lowercase
lis = list(ascii_lowercase)
print "".join( [lis[int(random() * 26)] for _ in xrange(1000000)] )
попробуйте превратить некоторую его часть в C++ или другой скомпилированный язык. Это почти гарантированно сделает его быстрее. Python, к сожалению, не слишком быстр, особенно когда дело доходит до таких вещей. Попробуйте C++, C или Паскаль.
EDIT: Смотрите также Советы По Производительности Python
генерировать и писать кусками, которые имеют большую мощность 2 в размере.
возможно, используйте строку или массив из 26 строчных букв и случайным образом выберите тогда вместо генерации символов.
использовать случайные.выбор?
На 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