Используйте itertools.permutations()
для увеличения количества повторяющихся цифр, вплоть до 9, комбинируя их со всеми цифрами от 1 до 9 (чтобы предотвратить создание чисел с начальным 0).
from itertools import permutations
def generate_unique_numbers():
yield 0
for i in range(10):
for leading in '123456789':
if not i: # 1-digit numbers
yield int(leading)
continue
remaining_digits = '0123456789'.replace(leading, '')
for combo in permutations(remaining_digits, i):
yield int(leading + ''.join(combo))
Это генерирует все такие допустимые числа без необходимости что-либо пропускать. Таких чисел 8877691, от 0 до 9876543210:
>>> sum(1 for _ in generate_unique_numbers())
8877691
>>> next(generate_unique_numbers())
0
>>> for i in generate_unique_numbers(): pass
...
>>> i
9876543210
Несколько образцов вывода:
>>> from itertools import islice
>>> gen = generate_unique_numbers()
>>> list(islice(gen, 15))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15]
>>> list(islice(gen, 150, 165))
[204, 205, 206, 207, 208, 209, 210, 213, 214, 215, 216, 217, 218, 219, 230]
>>> list(islice(gen, 100000, 100015))
[542319, 542360, 542361, 542367, 542368, 542369, 542370, 542371, 542376, 542378, 542379, 542380, 542381, 542386, 542387]
>>> list(islice(gen, 1000000, 1000015))
[31279056, 31279058, 31279064, 31279065, 31279068, 31279084, 31279085, 31279086, 31279405, 31279406, 31279408, 31279450, 31279456, 31279458, 31279460]
Этот метод значительно быстрее, чем генерация всех чисел с помощью range(9876543211)
, а затем отфильтровывание тех, в которых повторяются цифры (что и делает Моисей Коледой):
>>> from timeit import timeit
>>> from itertools import islice
>>> def consume_n(it, n): next(islice(it, n, n), None)
...
>>> timeit('consume_n(gen(), 10000)', 'from __main__ import consume_n, unique_count as gen', number=100)
1.825788974761963
>>> timeit('consume_n(gen(), 10000)', 'from __main__ import consume_n, generate_unique_numbers as gen', number=100)
0.6307981014251709
Приведенный выше код генерирует только первые 10000 чисел для каждого подхода и повторяет эти тесты 100 раз. Мой подход легко в 3 раза быстрее!
Увеличьте количество (и уменьшите количество повторений, чтобы оно оставалось управляемым), и контраст возрастет:
>>> timeit('consume_n(gen(), 100000)', 'from __main__ import consume_n, unique_count as gen', number=10)
4.125269889831543
>>> timeit('consume_n(gen(), 100000)', 'from __main__ import consume_n, generate_unique_numbers as gen', number=10)
0.6416079998016357
Теперь разница выросла в 6 раз быстрее. Однократное создание первого миллиона чисел занимает 23 секунды с версией range
и 0,67 секунды с приведенным выше генератором:
>>> timeit('consume_n(gen(), 1000000)', 'from __main__ import consume_n, unique_count as gen', number=1)
23.268329858779907
>>> timeit('consume_n(gen(), 1000000)', 'from __main__ import consume_n, generate_unique_numbers as gen', number=1)
0.6738729476928711
И чем дальше по ряду вы идете, тем больше натуральных чисел нужно пропустить и разница в скорости только растет; например, все числа в диапазоне 8800000000-8899999999 должны быть пропущены, а подход range()
будет проверять все из них. Это 100 миллионов потерянных циклов!
Генератор может выдать все возможные такие числа за 6,7 секунды на моем ноутбуке:
>>> from collections import deque
>>> def consume(it): deque(it, maxlen=0)
...
>>> timeit('consume(gen())', 'from __main__ import consume, generate_unique_numbers as gen', number=1)
6.6731719970703125
Я не осмелился проверить, сколько времени занимает подход range()
; таких чисел чуть менее 9 миллионов, но подход range()
проверит около 10 миллиардов возможностей, в 10 000 раз больше чисел, чем необходимо.
person
Martijn Pieters
schedule
27.07.2016
len(str(i)) == len(set(str(i)))
- person joel goldstick   schedule 27.07.2016