10
\$\begingroup\$

Yesterday, I came up with a simple method to predict the next value in a sequence.

The method works like this:

  • Start with a sequence, say 1,4,9,16,25,36, call it Δ0.

  • Now, Δ1 is the difference between every adjacent element in Δ0. (The first element is left unchanged). For our chosen sequence, this is 1,3,5,7,9,11.

  • In general, Δn is the difference between every adjacent element in Δn-1.

┌──────────────────┐
│Δ0: 1,4,9,16,25,36│
│Δ1: 1,3,5,7,9,11  │
│Δ2: 1,2,2,2,2,2   │
│Δ3: 1,2,0,0,0,0   │
│Δ4: 1,1,0,0,0,0   │
│Δ5: 1,0,-1,0,0,0  │
│Δ6: 1,-1,-1,1,0,0 │
│...               │
└──────────────────┘

Now we pick first Δn where the sum of the absolute of each value is less than than sum of the absolute values of Δn+1 . In this case it is Δ5: 1,0,-1,0,0,0. Now we duplicate the last value. (1,0,-1,0,0,0,0). Now we repeatedly take the running sums of this list n times, successfully undoing all the "difference between every adjacent element" function, but because we have an extra element, we will have an extra element in this new sequence. In our chosen sequence, this new sequence is 1,4,9,16,25,36,49.

Another example would be the sequence 2,5,3,9,6,2,3(unlike the previous sequence, this one doesn't follow a clear pattern). The sum of the absolutes of this sequence is 30. Δ1 of this sequence is 2,3,-2,6,-3,-4,1. The sum of the absolutes this time is 21. We continue, Δ2 is 2,1,-5,8,-9,-1,5. The sum of the absolutes is 31. Now, we can see that Δ1 is the first with a smaller absolute sum than the next Δ in the series. Now, we duplicate the last value of Δ1, giving 2,3,-2,6,-3,-4,1,1. Since this is Δ1, we take the running sums 1 time giving 2,5,3,9,6,2,3,4 as a final result.

Here's my code.

def runningSums(lst):
    res = [lst[0]]
    for elem in lst[1:]:
        res.append(res[-1] + elem)
    return res
def antiRunningSums(lst):
    res = [lst[0]]
    for i in range(1,len(lst)):
        res.append(lst[i] - lst[i-1])
    return res
def predict(lst):
    deriv = 0
    while True:
        nxt = antiRunningSums(lst)
        if sum(map(abs, nxt)) > sum(map(abs, lst)):
            break
        lst = nxt
        deriv += 1
    lst.append(lst[-1])
    for i in range(deriv):
        lst = runningSums(lst)
    return lst

# Example call. Correctly gives back [1,4,9,16,25,36,49].
print predict([1,4,9,16,25,36]) 
\$\endgroup\$
  • 3
    \$\begingroup\$ It also fails to find the right next value for: predict([n**n for n in range(7)]) and predict([1,-1,1,-1]) \$\endgroup\$ – Graipher Jul 7 '16 at 19:37
  • 1
    \$\begingroup\$ For which kind of sequences does this algoritmh work? If you do not give is sufficient context to the applicability of your algoritmh (exactly in which cases it should work) the question is unclear. \$\endgroup\$ – Caridorc Jul 7 '16 at 19:40
  • 3
    \$\begingroup\$ @Caridorc: I don't want to sound rude, but could you please give feedback on the code itself, and not so much on the algorithm? I know that the algorithm doesn't work for all sequences, and that is isn't perfect, I came up with it just to give rough estimates on simple sequences. \$\endgroup\$ – Loovjo Jul 7 '16 at 19:54
  • 1
    \$\begingroup\$ Nowadays, the #1 method for predicting the next number from a sequence (assuming the sequence has come up in a "natural" way) is to look it up in the Online Encyclopedia of Integer Sequences. \$\endgroup\$ – syb0rg Jul 7 '16 at 20:21
  • 1
    \$\begingroup\$ @Caridorc those described by a polynomial equation, of degree strictly less than the number of examples. \$\endgroup\$ – hobbs Jul 8 '16 at 3:10
4
\$\begingroup\$

antiRunningSums

You should use a pairwise helper to raise abstraction:

def antiRunningSums(lst):
    return [b - a for a, b in pairwise(lst)]

pairwise can easily be found in the itertools documentation.

runningSums

I think that referencing the list while you are building it is overcomplicated, I would use a variable to be incremented:

def runningSums(lst):
    s = 0
    for addend in lst:
        s += addend
        yield s

To obtain a list call list on the result of this function.

\$\endgroup\$
  • \$\begingroup\$ Your recommendation for antiRunningSums is better than what I was thinking of with enumerate. \$\endgroup\$ – Graipher Jul 7 '16 at 20:06
2
\$\begingroup\$

Apart from the to be specified applicability, I see only minor PEP8 nitpicks:

  1. Function names should be in snake_case.
  2. There should be two empty lines after a function definition (allowing easy copy-n-paste into the console)
  3. There should be spaces in comma-separated argument lists (in antiRunningSums one is missing)

Performance-wise: For long lists it becomes cheaper to pre-allocate the list to the right length and override elements than appending to a list.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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