Есть ли более Pythonic способ кодирования этого рекуррентного отношения re: OEIS A077947

Я работаю над тезисом о последовательностях Якобсталя (A001045) и о том, как их можно рассматривать как состоящие из некоторого числа различных подпоследовательностей. Я сделал комментарий к A077947, указав это, и включил программу на Python. К сожалению, написанная программа оставляет желать лучшего, и поэтому я, конечно, хотел обратиться к Stack, чтобы посмотреть, знает ли кто-нибудь здесь, как улучшить код!

Вот код:

a=1
b=1
c=2
d=5
e=9
f=18
for x in range(0, 100):
  print(a, b, c, d, e, f)
  a = a+(36*64**x)
  b = b+(72*64**x)
  c = c+(144*64**x)
  d = d+(288*64**x)
  e = e+(576*64**x)
  f = f+(1152*64**x) 

Я объясняю причины этого следующим образом:

Последовательность A077947 генерируется 6 цифровыми последовательностями, сохраняющими корни, сшитыми вместе; согласно коду Python эти последовательности инициируются при начальных значениях af. Число итераций, необходимое для вычисления данного A077947 a(n), составляет ~n/6. Код при выполнении возвращает все значения для A077947 вплоть до диапазона (x) или ~x*6 членов A077947. Я нахожу повторяющиеся цифровые корни интересными, так как я ищу периодическое сохранение цифровых корней в последовательностях как метод для определения закономерностей в данных. Например, последовательности сохранения цифрового корня позволяют анализировать временные ряды больших наборов данных при оценке истинного или ложного состояния для аварийных сигналов в крупных ИТ-экосистемах, которые подвергаются обслуживанию (среды mod7); такой анализ также связан с прогнозированием потребительского спроса / моделей поведения. Используя эти методы анализа, разделение A077947 на 6 последовательностей, сохраняющих цифровой корень, должно было уменьшить сложность; код Python воспроизводит A077947 через 6 "каналов" со начальными значениями af. Этот длинный абзац сводится к утверждению: "Цифровые корни терминов последовательности повторяются в схеме (1, 1, 2, 5, 9, 9)". Большее утверждение состоит в том, что все последовательности, цифровые корни которых повторяются с шаблоном, могут быть разделены / разделены на равное количество различных последовательностей, и эти последовательности могут быть вычислены независимо. Был щедрость, связанная с этой последовательностью.

Этот код уродлив, но я не могу получить правильный ответ, не написав его таким образом;

Я не понял, как написать это как функцию из-за того, что я не могу получить значения рекуррентности для правильного хранения в функции.

Поэтому, конечно, если это даст хорошие результаты, мы надеемся связать обсуждение со ссылками OEIS.

Вот ссылка на последовательность: https://oeis.org/A077947

3 ответа

Решение

Вот альтернативный способ сделать это без второго цикла for:

sequences   = [ 1,  1,  2,   5,   9,   18   ]
multipliers = [ 36, 72, 144, 288, 576, 1152 ]

for x in range(100):
    print(*sequences)
    sequences = [ s + m*64**x for s,m in zip(sequences,multipliers) ]

[РЕДАКТИРОВАТЬ] Глядя на значения, я заметил, что эту конкретную последовательность также можно получить с:

N [i + 1] = 2 * N [i] + (-1,0,1 в ротации)

или же

N [i + 1] = 2 * N [i] + i mod 3 - 1 (при условии, что индекс равен нулю)

i  N[i] [-1,0,1]             N[i+1]
0  1    -1   --> 2*1 - 1 --> 1
1  1     0   --> 2*1 + 0 --> 2
2  2     1   --> 2*2 + 1 --> 5
3  5    -1   --> 2*5 - 1 --> 9
4  9     0   --> 2*9 + 0 --> 18
... 

Таким образом, более простой цикл для создания последовательности может быть:

n = 1
for i in range(100):
    print(n)
    n = 2*n + i % 3 - 1

Использование функции Reduction от functools может сделать это еще более кратким:

from functools import reduce

sequence = reduce(lambda s,i: s + [s[-1]*2 + i%3 - 1],range(20),[1])
print(sequence)

>>> [1, 1, 2, 5, 9, 18, 37, 73, 146, 293, 585, 1170, 2341, 4681, 9362, 18725, 37449, 74898, 149797, 299593, 599186]

Используя ваш многоканальный подход и предложенную мной формулу, это даст:

sequences   = [ 1,  1,  2,   5,   9,   18   ]
multipliers = [ 36, 72, 144, 288, 576, 1152 ]

allSequences = reduce(lambda ss,x: ss + [[ s + m*64**x for s,m in zip(ss[-1],multipliers) ]],range(100),[sequences])
for seq in allSequences: print(*seq) # print 6 by 6

[EDIT2] Если у всех ваших последовательностей будет одинаковый шаблон (т.е. начальные каналы, множители и формула расчета), вы можете обобщить вывод таких последовательностей в функцию, поэтому для каждой последовательности требуется только одна строка:

def printSeq(calcNext,sequence,multipliers,count):
    for x in range(count):
        print(*sequence)
        sequence = [ calcNext(x,s,m) for s,m in zip(sequence,multipliers) ]

printSeq(lambda x,s,m:s*2+m*64**x,[1,1,2,5,9,18],multipliers=[36,72,144,288,576,1152],count=100)

[EDIT3] Улучшение функции printSeq.

Я считаю, что вам не всегда нужен массив множителей для вычисления следующего значения в каждом канале. Улучшение функции будет заключаться в предоставлении индекса канала для лямбда-функции вместо множителя. Это позволит вам использовать массив множителей, если вам нужно, но также позволит вам использовать более общие вычисления.

def printSeq(name,count,calcNext,sequence):
    p = len(sequence)
    for x in range(count):
        print(name, x,":","\t".join(str(s) for s in sequence))
        sequence = [ calcNext(x,s,c,p) for c,s in enumerate(sequence) ]

Функция лямбда имеет 4 параметра и, как ожидается, вернет следующее значение последовательности для указанного канала:

s : current sequence value for the channel
x : iteration number
c : channel index (zero based)
p : number of channels

Таким образом, использование массива внутри формулы будет выражать это так:

printSeq("A077947",100,lambda x,s,c,p: s + [36,72,144,288,576,1152][c] * 64**x, [1,1,2,5,9,18])

Но вы также можете использовать более общую формулу, основанную на индексе канала (и количестве каналов):

printSeq("A077947",100,lambda x,s,c,p: s + 9 * 2**(p*x+c+2), [1,1,2,5,9,18])

или ( 6 каналов на основе 2*S + i%3 - 1):

printSeq("A077947",100,lambda x,s,c,p: 64*s + 9*(c%3*2 - (c+2)%3 - 1) ,[1,1,2,5,9,18])
printSeq("A077947",100,lambda x,s,c,p: 64*s + 9*[-3,1,2][c%3],[1,1,2,5,9,18])

Я рассуждаю здесь так: если у вас есть функция, которая может вычислить следующее значение на основе текущего индекса и значения в последовательности, вы сможете определить шаговую функцию, которая будет вычислять значение, которое на N индексов дальше.

Дано F(i,S[i]) -> i+1,S[i+1]

F2(i,S[i]) --> i+2,S[i+2] = F(F(i,S[i]))
F3(i,S[i]) --> i+3,S[i+3] = F(F(F(i,S[i])))
...
F6(i,S[i]) --> i+6,S[i+6] = F(F(F(F(F(F(i,S[i]))))))
...
Fn(i,S[i]) --> i+n,S[i+n] = ...

Это всегда будет работать и не должно требовать массив множителей. Большую часть времени должно быть возможно упростить Fn, используя простую алгебру.

например, A001045: F(i,S) = i+1, 2*S + (-1)**i

printSeq("A001045",20,lambda x,s,c,p: 64*s + 21*(-1)**(x*p+c),[0,1,1,3,5,11])

Обратите внимание, что начиная с 3-го значения, следующее значение в этой последовательности может быть вычислено без знания индекса:

A001045: F (S) = 2 * S + 1 - 2 * 0 ** ((S + 1)% 4)

Вот альтернативная версия с использованием генераторов:

def Jacobsthal():
    roots = [1, 1, 2, 5, 9, 18]
    x = 0
    while True:
        yield roots
        for i in range(6):
            roots[i] += 36 * 2**i * 64**x
        x += 1

И вот безопасный способ его использования:

j = Jacobsthal()
for _ in range(10):
    print(j.send(None))

Это будет вести себя идентично вашему коду, и, возможно, красивее. Вы, вероятно, увидите способы сделать магические константы менее произвольными.

factors = [ 1, 1, 2, 5, 9, 18 ]
cofactors = [ 36*(2**n) for n in range(6) ]

for x in range(10):
    print(*factors)
    for i in range(6):
        factors[i] = factors[i] + cofactors[i] * 64**x

Чтобы вычислить только одну из подпоследовательностей, было бы достаточно сохранить i исправлено во время итерации.

Как видно из аннотации OEIS, вам нужны только 3 начальных значения и 1 рекурсия из 3 предыдущих элементов последовательности, a(n-1), a(n-2) и a(n-3). После этого вы легко сможете получить серию.

# a(n-1)+a(n-2)+2*a(n-3)
# start values: 1, 1, 2

a1, a2, a3 = 1,1,2 #initial vales
m3, m2, m1 = 1,1,2 #multipliers for a(n-1):m3 a(n-2):m2 and a(n-3):m1

print a1, a2, a3,
for i in range(16):
    a1,a2,a3=a2,a3,a3*m3+a2*m2+a1*m1
    print a3,

Это дает вам 1 1 2 5 9 18 37 73 146 293 585 1170 2341 4681 9362 18725 ...

Другие вопросы по тегам