20
$\begingroup$

Just out of interest I tried to solve a problem from "Recent" category of Project Euler ( Digit Sum sequence ). But I am unable to think of a way to solve the problem efficiently. The problem is as follows ( in the original question sequence has two ones in beginning , but it does not change the sequence ) :

The Digit Sum sequence is 1,2,4,8,16,23,28,38,49.... where the $n^{th}$ term of sequence is sum of digits preceding it in the sequence. Find the $10^{15}th$ term of the sequence.

The naive solution can't be implemented because it takes a lot of time. I tried to reduce the problem to a case of matrix exponentiation ( that would takes $O(log ( 10^{15}))$ amount of time ) but could not come up with such a recurrence fitting the linear criteria as recurrence for this sequence is quite peculiar. It can be seen that the sequence is governed by the recurrence :

$$ a_n = a_{n-1} + d( a_{n-1} ) ..... (1 )$$

where $a_n$ is $n^{th}$ term of the sequence and $d$ is a function which when given a natural number as input returns sum of digits of the number ( eg. $\;d(786)=21$ ). My second approach was to try to find some pattern in the sequence. It can be seen that the first few terms of the sequence can be written as

   a_1 = 1  
   a_2 = 1 + d( 1 )
   a_3 = 1 + d( 1 ) + d( 1 + d( 1 ) )
   a_4 = 1 + d( 1 ) + d( 1 + d( 1 ) ) + d( 1 + d( 1 ) + d( 1 + d( 1 ) ) )
   a_5 = 1 + d( 1 ) + d( 1 + d( 1 ) ) + d( 1 + d( 1 ) + d( 1 + d( 1 ) ) ) + d( 1 +  d(  
   1 ) + d( 1 + d( 1 ) ) + d( 1 + d( 1 ) + d( 1 + d( 1 ) ) ) )

From the pattern above it becomes that $n^{th}$ term of the sequence can be generated by the following method :

  1. Write $2^{n-1}$ $1$'s with addition symbol between them.
  2. Leaving the first $1$, then apply the function $d$ on the next $2^{0}$ terms then on next $2^{1}$ terms, then on next $2^{2}$ terms and so on.
  3. Then apply the above method recursively on arguments of each $d$ function applied.

for example if n=3 we perform the following manipulations:

    1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
    1 + d( 1 ) + d( 1 + 1 ) + d( 1 + 1 + 1 + 1 )
    1 + d( 1 ) + d( 1 + d(1) ) + d( 1 + d( 1 ) + d( 1 +d( 1 ) ) )

By dynamic programming I can generate the $n^{th}$ term using the above method in time $O(log ( 2^{10^{15}} ) )$, which again is no better than the naive solution.

EDIT 1
Another thing that can be observed is that $d(a_n) = d(2^{n-1})$. For example $d(a_6)=d(23)=d(32)=5$. But I am unable to make use of this point. I again tried to find a linear recurrence relation ( for matrix exponentiation ) , but I am unable to find it.

EDIT 2

Following is the graph when the sequence is plotted for smaller range ( first $10^6$ terms of the sequence are plotted ). enter image description here

PS: I know it is not advisable to ask solutions from Project Euler. But I just want a new direction or a hint, as I have been moving in circles for past few days. If that is also unacceptable I can remove the question if suggested.

$\endgroup$
9
  • 1
    $\begingroup$ I feel like You are given a106 = 31054319. in the original Euler problem is a hint. $\endgroup$ Apr 21, 2016 at 9:56
  • $\begingroup$ @FilipHaglund that is not a hint. As by brute force alone I can calculate that value easily. It is just for checking your approach. $\endgroup$ Apr 21, 2016 at 10:14
  • 3
    $\begingroup$ Also on OEIS: oeis.org/A004207. $\endgroup$ Apr 22, 2016 at 15:08
  • $\begingroup$ @EvilJS could yes I plotted the the graph to an extend it increases gradually in a zig zag fashion. Could you elaborate your last point ""caching patterns..". $\endgroup$ Apr 22, 2016 at 17:47
  • $\begingroup$ Given that interesting patterns appear mod 9, does anything interesting happen if we look at the sequence mod 11 or mod 99? The value mod 11 can be derived from the sum of the odd-indexed digits and the sum of the even-indexed digits. The value mod 99 can be derived from the sum of adjacent-pairs-of-digits. $\endgroup$
    – D.W.
    Apr 23, 2016 at 4:27

2 Answers 2

4
+400
$\begingroup$

Your sequence is described in oeis.org/A004207 as digits sum. There are some good points like sequence mod 9 has repeating pattern $(1, 2, 4, 8, 7, 5)^\infty$, it shares digital roots with oeis.org/A065075 and oeis.org/A001370. If those properties are useful is open problem (because there is no closed form equation for $n-th$ number).

There are some properties of this sequence worth mentioning:
When you calculate $n-th$ number, you need to store only counter (to know which numer it was) and the number itself. To restart there is nothing more needed, as the next number is current number + sum of it's digits.

Taking some steps to ensure speed at first it is good to put numbers into array, avoiding naive mod and div calculations, which are expensive. This gives speedup by constant, but looking at times it does matter.

From starting point you can calculate next one, and next, and it works upto some point, this very point is number of digits change.
What is more important, patterns are changing with increase of numbers.
Digit sums are small in compare to numbers itself, so only the part of number will change on the most operations.
So what really can we cache?

We know that with two numbers with the same sum of digits the addition to get the next number will be the same. What about the next one?

sasha

Spoiler alert, below is quite explicit cache pattern

It depends on additional conditions, like the numbers that does not change in the run, I will call it shift, starting amount as start.

Taking some arbitrary run, like $100$, and taking start from $0$ to $9$ we can cache every pattern from starting point up to $100$, count number of elements (to know how to deal with counter, which is needed to give some $n-th$ number), and memorize it.

Ok. Until now any start is covered, what changes beyond $100$?
Unfortunately this patterns stop working, because if you try from $100$ making start equal $1$, the next number is calculated perfectly, but second one breaks.

Here we have to cover shift set to $1$ and start set to $0$. It also means calculating tables for different shifts.

Do we really need to calculate all of them? Not really, no.
Part of tables is just another one starting item further.
For example starting from $1, 2, 4, 8$ gives the same sequence shifted.
So do we need to calculate longer cache?
No, we calculate it up to shift change to pick up another run, so it will save a lot of memory.

Now when table is covered, we start from initial setting, pick sum from table, add counter and see that: from $1$ we get to $101$, update variables and jump to $218$, repeating the steps $305$ -> $406$ -> $517$ -> $607$ -> $719$ -> $805$ -> $904$ -> $1003$.
Ok. What now?

We can continue until shift is higher than calculated.
Going further we might build more runs on fly, precalculate bigger runs, or observe another patterns (like we can partialy reuse tables already calculated).
Take a look at different shifts like $100, 1000, 10000, 100000, 1000000...$ they all give the same run being the same environment for digit sums, hence we can use the very same tables.
Making bigger tables than $100$ elements speeds up process further, making bigger jumps at once.

$\endgroup$
0
4
$\begingroup$

Since you asked for " a new direction or a hint " and I don't know the answer, I'll leave this here, I hope it's helpful. some ideas:

It makes sense there would be a pattern mod 9, since

$\forall k > 1,k \in \mathbb{Z}\quad10^k \equiv 1 \mod 9$

Which you can prove by induction.

This means that all numbers are congruent to the sum of their digits mod 9.

Further, $a_n = d(a_n) \mod 9 \implies $ $$a_{n} = a_{n-1} + d(a_{n-1}) = 2d(a_{n-1}) \mod 9$$

If we keep expanding this recurrence we get

$a_{n} = 2^n \mod 9$

Which explains the pattern mod 9.

It also gets us $a_n = 9k + 2^n$. Every iteration we get a gap that is divisible by 9. How wide are those gaps?

Here's some less than general code:

import matplotlib.pyplot as plt
import numpy as np

#sum digits of n
def sum_digits(n):
    s = 0
    while n:
        s += n % 10
        n //= 10
    return s

#get the sequence to n digits
def calculate(n):
    retval = [1]
    for i in range(n):
        retval.append(retval[-1] + sum_digits(retval[-1]))
    return retval;

#empirically confirm that a_n = 2^n mod 9
def confirmPow2(a):
    count = 0
    for i in a[:10000]:
        if((i%9) != (2**count % 9)):
            print "false"
        count = count + 1

#find gaps divisible by 9 in a subset of a
def find9Gaps(a):
    count = 0
    S = []
    for i in a[:10000]:
         S.append(((2**count ) - i)/9)
         count = count + 1
    return S

#repeatedly sum the digits until they're less than 9...
#gives some interesting patterns
def repeatedDigitSum():
    for i in range(1000, 1100):
         print "=========for ",i
         while i > 9:
                 i = sum_digits(i)
                 print i 


a = calculate(10**6)
b = find9Gaps(a)
plt.plot(range(len(b[:100])), b[:100])
plt.show()

The plot (for the first 100) looks exponential, but I don't think it's perfect.

plot for gaps

Here's the output of

>>> plt.plot(range(len(b[5:60])), np.log2(np.array(b[5:60])))
>>> plt.show()

logarithmic plot of the gaps

The last thing I have is that it seems if you sum the digits of a number, and then sum the digits of the resulting number, and repeat this, you eventually get that number mod 9.

Makes sense given the above fact about powers of 10 mod 9.

$$n \equiv d(n) \equiv d(d(n)) \equiv \cdots \mod 9$$

It gives an interesting sequence of numbers though.

Edit: Apparently this is called a "digital root".

$\endgroup$
3
  • 1
    $\begingroup$ It was kinda commented at least three times. Also when you do plot which looks exponential to you, maybe you should use logarithm, inform about it on scale axis? If you plotted readable 10^16 terms I would be really impressed. $\endgroup$
    – Evil
    Apr 23, 2016 at 19:59
  • $\begingroup$ What was commented 3 times? People were saying there was a "pattern mod 9" but I felt like it wasn't clear what that was. I just did some exploring and commented what I had, since I don't think I will be able to continue working on this. Again, I don't have a solution, but the question didn't ask for one. $\endgroup$ Apr 23, 2016 at 20:35
  • $\begingroup$ Added a log plot per EvilJS suggestion, can't plot any larger because numpy breaks and I really don't have time to continue pursuing this problem $\endgroup$ Apr 23, 2016 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.