#230 - Fibonacci Words

For any two strings of digits, AA and BB, we define FA,BF_{A,B} to be the sequence (A,B,AB,BAB,ABBAB,)(A,B,AB,BAB,ABBAB,\dots) in which each term is the concatenation of the previous two.

Further, we define DA,B(n)D_{A,B}(n) to be the nthn^{\text{th}} digit in the first term of FA,BF_{A,B} that contains at least nn digits.

Example:

Let A=1415926535A = 1415926535, B=8979323846B = 8979323846. We wish to find DA,B(35)D_{A,B}(35), say.

The first few terms of FA,BF_{A,B} are: 1415926535897932384614159265358979323846897932384614159265358979323846141592653589793238468979323846141592653589793238461415926535 \\ 8979323846 \\ 14159265358979323846 \\ 897932384614159265358979323846 \\ 1415926535897932384689793238461415\mathbf{\color{red}9}265358979323846

Then DA,B(35)D_{A,B}(35) is the 35th35^{\text{th}} digit in the fifth term, which is 9.

Now we use for AA the first 100 digits of π\pi behind the decimal point:

1415926535897932384626433832795028841971693993751014159265358979323846264338327950288419716939937510

5820974944592307816406286208998628034825342117067958209749445923078164062862089986280348253421170679

and for BB the next hundred digits:

8214808651328230664709384460955058223172535940812882148086513282306647093844609550582231725359408128

4811174502841027019385211055596446229489549303819648111745028410270193852110555964462294895493038196

Find n=01710n×DA,B((127+19n)×7n)\sum_{n=0}^{17}10^n\times D_{A,B}((127+19n)\times 7^n)

Background

In the summations, if n=17n=17, we have a very large argument for DA,BD_{A,B}. The string that is generated until we have that many digits will be too large to keep in memory. Because these are “Fibonacci” words, the number of individual strings i.e. AA’s and BB’s that are present at each iteration will grow according to Fibonacci words.

The sequence A,B,AB,BAB,ABBAB,BABABBAB,A,B,AB,BAB,ABBAB,BABABBAB,\dots goes 1,1,2,3,5,81, 1, 2, 3, 5, 8.

Methodology

The main question is as follows: Do we need to compute the strings explicitly? Is it possible to find DA,B(n)D_{A,B}(n) without computing a string that is at least nn digits long? Here’s an observation. In the smaller example, both AA and BB were 10 digits long. When computing DA,B(35)D_{A,B}(35), the fifth string was 50 digits, so a 35th35^{\text{th}} digit existed. It was 50 digits long, because the fifth term in the Fibonacci sequence was 5, and 5×10=505\times 10 = 50.

The fifth Fibonacci word is ABBABABBAB. Since we need the 35th35^{\text{th}} digit, and each string is 10 digits long, this means the fifth entry in the fourth string is our answer. The fourth string is AA and and the fifth digit is 9, our answer.

Ok, so it doesn’t seem like we need to compute entire string explicitly. However, we can actually go one step further, and not even need the strings of AA’s and BB’s!

Working backwards

We can essentially use the Fibonacci formula in reverse. Again, using our example, we know we need the fifth Fibonacci word, which corresponds to the Fibonacci number of 5. However, we know 5=2+35=2+3 (the previous two Fibonacci numbers, and hence the previous two Fibonacci words). Thus, the first 20 digits is one “word”, and the next 30 digits is another “word”. Because 35>2035>20, the 35th35^{\text{th}} digit of the 50-digit word, is actually the 15th15^{\text{th}} digit of the 30-digit word (as 5035=1550-35=15).

We just made our problem smaller. Instead of needing the 35th35^{\text{th}} digit of a 5050-digit word, we now need the 15th15^{\text{th}} of a 3030-digit word. Let’s repeat one more step. 3030 corresponds to the Fibonacci number of 33, and 3=1+23=1+2. We have a 1010-digit word (which is BB in this case) attached to a 2020-digit word (ABAB). Next, 15>1015>10, and 2015=520-15=5, so we need the 5th5^{\text{th}} digit of ABAB. This is easy, as it is just the 5th5^{\text{th}} of AA, which is 99, our answer.

Using this “reduction” method, we were able to completely reduce the problem into finding a digit in the original strings of AA and BB. It was relatively simple to convert the lengths to their corresponding Fibonacci numbers, because AA and BB were the same length, but you can make a similar argument for different length strings. Thankfully, in the larger problem, AA and BB are still the same length.

The only necessary calculation we need is a list of all the Fibonacci numbers. However, Fibonacci numbers grow exponentially, and so we shouldn’t need too many. We can use its explicit formula to find how many we need.

Code and Implementation

The code is relatively straight forward, and I chose an iterative approach to the reduction. For a recursive approach, you would need to keep track of which Fibonacci number your string refers to, and the base case would correspond to having an index which is less than the length of the original string.

In our function, we take nn, AA, BB, and a list of Fibonacci numbers that are big enough. I also include a small one-off function that calculates the largest Fibonacci number we need.

def fib_index(n):
    return int((math.log(n) + math.log(5) / 2) / math.log((1 + math.sqrt(5)) / 2)) + 1

def dab(n, A, B, fibs: list):
    """
    Finds D_a,b(n) using the Fibonacci word expansion.
    """
    # There's no need to keep computing the next Fibonacci string
    # until the string is long enough. Since each string is built from A
    # and B, we will cut-down the needed string length, basically computing
    # Fibonacci in reverse until we reach either A or B.
    # For example, len(A) = len(B) = 10, and n = 35, this means the fifth Fibonacci
    # expansion has at least 50 characters (corresponding to F value of 5).
    # 5 = 2 + 3, since 35 >= 20, the character we need is in the last 30 charas,
    # and it would be the 15th chara. 3 = 1 + 2, again it's in the second part (20 - 15 = 5th chara).
    # 2 = 1 + 1 (A + B), 5 < 10, so the 5th character of A is 9, which is the answer.
    # One issue, we need the precomputed Fibonacci numbers...
    # ASSUME A AND B HAVE THE SAME LENGTH.
    if n <= 10:
        return A[n - 1]
    str_length = len(A)
    # Find where we start at...
    i = 0
    while fibs[i] * str_length < n:
        i += 1
    # Keep going until we land at one of the starting two strings (either index 0 or index 1 in fibs)
    while i >= 2:
        # Find which part of the string we need to jump to, by comparing the previous two
        # Fibonacci values and the current value of n.
        if n > fibs[i - 2] * str_length:
            # We want i - 1, adjust n as necassary.
            n -= fibs[i - 2] * str_length
            i -= 1
        else:
            # We want i - 2, in this case, n doesn't change
            i -= 2
    # Take whichever string we need...
    return int(A[n - 1]) if i == 0 else int(B[n - 1])

All that’s left is to calculate our summation using this function.

A = '1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679'
B = '8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196'
# Using the max possible value for n, we can find what Fibonacci number we calculate up to.
# Divide n by the length of A or B.
L = 17
MAX_N = (127 + 19 * L) * 7 ** L
MAX_FIB = MAX_N // 100
fval = fib_index(MAX_FIB) + 1
print(f'Need {fval} Fibonacci numbers.')
# Precompute Fibonacci numbers...
fibs = [1, 1]
for _ in range(fval - 2):
    fibs.append(fibs[-1] + fibs[-2])
print(fibs)

s = sum(10 ** n * dab(7 ** n * (127 + 19 * n), A, B, fibs) for n in range(L + 1))

print(s)

Running this, we get

Need 75 Fibonacci numbers.
850481152593119296
0.00019055799998568546 seconds.

Therefore, the value of the summation is 850481152593119296, and it turns out we needed 75 Fibonacci numbers (up to 2111485077978050).

0