Что такое касательные числа? Ну, это следующие серии:
Код: Выделить всё
1
2
16
272
7936
353792
22368256
1903757312
209865342976
29088885112832
4951498053124096
1015423886506852352
246921480190207983616
70251601603943959887872
23119184187809597841473536
8713962757125169296170811392
3729407703720529571097509625856
1798651693450888780071750349094912
970982810785059112379399707952152576
583203324917310043943191641625494290432
Я реализовал бесконечный генератор касательных чисел, используя следующую формулу: a(n) = Sum_{k = 1..n-1} binomial(2 * n-2, 2 * k-1) * a(k) * a(n-k), с a(1) = 1, но я не буду вставлять ее сюда, так как она довольно длинная и не Суть вопроса в том, что она не так эффективна, как следующая функция, которую я опубликую ниже. Чтобы этот вопрос сосредоточился на одной конкретной теме, я не буду вставлять сюда код, но вы можете просмотреть его по этой ссылке.
Я реконструировал следующую функцию Питера Лушни, которую нашел на связанной странице OEIS, она занимает около 2/3 времени, которое требуется моей исходной функции генератора для того же ввода.
Функция выглядит следующим образом:
Код: Выделить всё
def A000182_list(n):
T = [0 for i in range(1, n+2)]
T[1] = 1
for k in range(2, n+1):
T[k] = (k-1)*T[k-1]
for k in range(2, n+1):
for j in range(k, n+1):
T[j] = (j-k)*T[j-1]+(j-k+2)*T[j]
return T[1:]
Что делает эта строка: T = [0 for i in range(1, n+2)]? Ну, очевидно, что он инициализирует список длиной n-1, заполненный нулями. И это неэффективно.
Вторая строка: T[1] = 1, таким образом, элементу с индексом 1 присваивается значение 1. Мы видим, что индексация 0 никогда не используется и не доступна в коде. Поэтому нам нужен только список длины n.
Поэтому я реорганизовал его так: result = [1] * n. Повторение списка с помощью оператора * — наиболее эффективный способ получить список длины n в Python.
Что делает следующий цикл?
Код: Выделить всё
for k in range(2, n+1):
T[k] = (k-1)*T[k-1]
Другими словами, он просто вычисляет факториалы, каждый член является продуктом предыдущего термина и текущего положительного целого числа, которое увеличивается на 1 на каждой итерации. Поскольку термины используются сразу же на следующей итерации и ровно один раз после присвоения, T[k-1] требует избыточной индексации списка.
Я переработал код следующим образом:
Код: Выделить всё
result = [1] * n
last = 1
for i in range(1, n):
result[i] = last = last * i
Код: Выделить всё
for k in range(2, n+1):
for j in range(k, n+1):
T[j] = (j-k)*T[j-1]+(j-k+2)*T[j]
Код: Выделить всё
In [31]: [list(range(i, 11)) for i in range(2, 11)]
Out[31]:
[[2, 3, 4, 5, 6, 7, 8, 9, 10],
[3, 4, 5, 6, 7, 8, 9, 10],
[4, 5, 6, 7, 8, 9, 10],
[5, 6, 7, 8, 9, 10],
[6, 7, 8, 9, 10],
[7, 8, 9, 10],
[8, 9, 10],
[9, 10],
[10]]
И так же, как и в случае с факториалом, каждый член рассчитывается с использованием предыдущего термина путем индексации. И нам не нужно индексировать T[j - 1], если мы присваиваем его переменной.
Итак, это мой рефакторинг кода:
Код: Выделить всё
def A000182_fast(n):
result = [1] * n
last = 1
for i in range(1, n):
result[i] = last = last * i
for k in range(1, n):
b = 2
last = result[k - 1]
for a, c in enumerate(range(k, n)):
result[c] = last = a * last + b * result[c]
b += 1
return result
Код: Выделить всё
In [34]: A000182_fast(32)
Out[34]:
[1,
2,
16,
272,
7936,
353792,
22368256,
1903757312,
209865342976,
29088885112832,
4951498053124096,
1015423886506852352,
246921480190207983616,
70251601603943959887872,
23119184187809597841473536,
8713962757125169296170811392,
3729407703720529571097509625856,
1798651693450888780071750349094912,
970982810785059112379399707952152576,
583203324917310043943191641625494290432,
387635983772083031828014624002175135645696,
283727921907431909304183316295787837183229952,
227681379129930886488600284336316164603920777216,
199500252157859031027160499643195658166340757225472,
190169564657928428175235445073924928592047775873499136,
196535694915671808914892880726989984967498805398829268992,
219523439106761591280258358007964245987752702449505540243456,
264239411287900883270178745605712648488731058170551223831232512,
341838301335718580350174449297951396847081443826785448952307122176,
474090194351342155974522582010891145370303013658973457042263121068032,
703237958001393736999896827714634659411015090272684227831001161763127296,
1113255345330866700339746218047088280783690575394538814699382505942970007552]
In [35]: A000182_fast(32) == A000182_list(32)
Out[35]: True
In [36]: %timeit A000182_fast(256)
15.1 ms ± 109 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [37]: %timeit A000182_list(256)
15.6 ms ± 211 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Остановился ли я на этом? Конечно, нет. Хотя у меня есть работающий эффективный код, он не решает проблему, которую я намеревался решить.
Я хочу генерировать числа касательных по требованию, то есть я хочу, чтобы что-то вроде tangent_numbers(n) возвращало первые n чисел касательных, что я уже сделал, но я также хочу, чтобы код полностью избегал избыточных вычислений, я хочу, чтобы код запоминал уже сгенерированные числа касательных и сохранял их в переменной. Таким образом, если n меньше или равно количеству уже сгенерированных чисел касательных, код вообще не должен выполнять никаких фактических вычислений и вместо этого должен немедленно возвращать фрагмент этой глобальной переменной.
И затем, для числа, большего, чем длина уже сгенерированной последовательности, код должен заполнить глобальную последовательность чисел касательных с помощью [0] * (число - длина), а затем, начиная с длины индекса, он должен продолжить работу сразу после того, где он остановился, и он должен сгенерировать следующее термины, используя уже сгенерированные термины, и сохраняйте новые термины в глобальной переменной и возвращайте эту глобальную переменную после того, как это будет сделано.
Я выполнил функцию возобновления, используя мой бесконечный генератор и itertools.islice, однако, как я объяснил, это не так быстро, как конечная функция.
Я пытался обернуть код в класс, но это не работает:
Код: Выделить всё
OPS = []
class Tangent_numbers:
def __init__(self, n: int = 0):
if not isinstance(n, int) or n < 0:
raise ValueError("argument n must be a nonnegative integer")
self.series = [1]
self.factorial = 1
self.length = 1
if n > 1:
self.extend(n)
def extend(self, n: int) -> None:
if not isinstance(n, int) or n 0
b = start + 2
for a, c in enumerate(range(k + start, n), start=start):
series[c] = a * series[c - 1] + b * series[c]
OPS.append((k, a, b, c))
b += 1
self.length = (length, n)[length < n]
Код: Выделить всё
A000182_fastИ это не работает:
Код: Выделить всё
In [39]: tan1 = Tangent_numbers()
In [40]: tan1.extend(32)
In [41]: tan1.series
Out[41]:
[1,
2,
16,
272,
7936,
353792,
22368256,
1903757312,
209865342976,
29088885112832,
4951498053124096,
1015423886506852352,
246921480190207983616,
70251601603943959887872,
23119184187809597841473536,
8713962757125169296170811392,
3729407703720529571097509625856,
1798651693450888780071750349094912,
970982810785059112379399707952152576,
583203324917310043943191641625494290432,
387635983772083031828014624002175135645696,
283727921907431909304183316295787837183229952,
227681379129930886488600284336316164603920777216,
199500252157859031027160499643195658166340757225472,
190169564657928428175235445073924928592047775873499136,
196535694915671808914892880726989984967498805398829268992,
219523439106761591280258358007964245987752702449505540243456,
264239411287900883270178745605712648488731058170551223831232512,
341838301335718580350174449297951396847081443826785448952307122176,
474090194351342155974522582010891145370303013658973457042263121068032,
703237958001393736999896827714634659411015090272684227831001161763127296,
1113255345330866700339746218047088280783690575394538814699382505942970007552]
In [42]: good_ops = OPS.copy()
In [43]: OPS.clear()
In [44]: tan2 = Tangent_numbers()
In [45]: tan2.extend(16)
In [46]: tan2.extend(32)
In [47]: bad_ops = OPS.copy()
In [48]: OPS.clear()
In [49]: tan2.series
Out[49]:
[1,
2,
16,
272,
7936,
353792,
22368256,
1903757312,
209865342976,
29088885112832,
4951498053124096,
1015423886506852352,
246921480190207983616,
70251601603943959887872,
23119184187809597841473536,
8713962757125169296170811392,
2904913076573872266203498954016294449446912,
4691853834639623592590771373697685743890071552,
5130358638144401639057729513343066448970081370112,
4915808771679000034913126880856135752591161113444352,
4532390344824939077129321365233314869866731663798566912,
4203608535388999900804817047351188778825742479186567626752,
4016075105391784162392416168597886026774711180596874221453312,
4006317907309964706114200066419926732731565593907176950571466752,
4206222056177413192157298446480182062802619739270396407035369357312,
4669250237392028942296888593639384792643032067338268907267585686372352,
5494543666053818716762261654491210913657871572416499541257406325993766912,
6862845392986161471745129519783028922048665371026568262534783173188021911552,
9102416468449937394166752814219701116933808763260683657119736372726144394330112,
12818533459855341263183886505243846034060346753317615985094136744918816751827812352,
19157416044307854900780627577605126931202540354102138452037843767399405302510530854912,
30362182612272673641771108733390016965056260526780787823865261029201116235800577941962752]
In [50]: set(good_ops) ^ set(bad_ops)
Out[50]: set()
In [51]: from collections import Counter
In [52]: Counter(good_ops) - Counter(bad_ops)
Out[52]: Counter()
In [53]: Counter(bad_ops) - Counter(good_ops)
Out[53]: Counter()
Я понятия не имею, почему это так, математика мне не под силу, мне не удалось перепроектировать использованную математику. Я не могу исправить ошибку.
Как правильно реализовать возобновление?
Подробнее здесь: https://stackoverflow.com/questions/799 ... -resumable
Мобильная версия