'How to calculate a list based on multiple results from items in a list?

I have a list A=[a,b,c,d]. I need to calculate a new list B based on operations between each item in A.

B= [a, b-(a), c-(a+(b-a)), d-(a+(b-a)+(c-(a+(b-a)))) ]

Is there a Pythonic way of doing this? List A is not always a 4 item list, so the solution needs to be generalizable to lists of arbitrary length. Thanks in advance.



Solution 1:[1]

Observe that the expression for your list can be simplified to:

B = [a, b-a, c-b, d-c]

With this in mind, we can use a list comprehension:

[y - x for x, y in zip([0] + data, data)]

For example,

data = [1, 2, 7, 6]
result = [y - x for x, y in zip([0] + data, data)]
print(result)

outputs:

[1, 1, 5, -1]

Solution 2:[2]

All your terms cancel out (c-(a+(b-a)) simplifies to c - b, d-(a+(b-a)+(c-(a+(b-a)))) simplifies to d - c), so the real algorithm here is that each term is equal to the matching term minus the prior term. This simplifies things dramatically:

B = [A[0]]  # Initial term has no prior to subtract from it
B += [x - y for x, y in zip(A[1:], A)]  # All other terms computed by subtracting term n - 1 from term n

If you want to one-line this (ignoring imports) you can stick a virtual 0 in to get the results for the first element without explicitly special-casing it:

from itertools import chain  # At top of file

B = [x - y for x, y in zip(A, chain([0], A))]

If you love using map and friends for microoptimizations, you could replace the latter with:

from operator import sub  # At top of file

B = [*map(sub, A, chain([0], A))]

and push all the work to the C layer (no per-element bytecode execution).

Solution 3:[3]

Two solutions that don't assume that things "cancel out" (because that is wrong already for standard types like float and Counter, as shown below).

If I understand the pattern correctly, the first B-value shall be the first A-value and then each next B-value shall always be the next A-value minus the sum of all previous B-values. One way to do that:

B = []
for a in A:
    if not B:
        b = sumB = a
    else:
        b = a - sumB
        sumB = sumB + b
    B.append(b)

Fun one using itertools.accumulate and operator.sub:

B = A[:1]
B += map(sub, A[1:], accumulate(B))

Tests:

                        A = [31, 41, 59, 26]
reference                   [31, 10, 18, -33]
subtract_neighbors  correct [31, 10, 18, -33]
loop                correct [31, 10, 18, -33]
fun                 correct [31, 10, 18, -33]

                        A = [1, 1, 1e-20, 1e-20]
reference                   [1, 0, -1.0, 1e-20]
subtract_neighbors  wrong   [1, 0, -1.0, 0.0]
loop                correct [1, 0, -1.0, 1e-20]
fun                 correct [1, 0, -1.0, 1e-20]

                        A = [Counter(), Counter({None: 1}), Counter(), Counter({None: 1})]
reference                   [Counter(), Counter({None: 1}), Counter(), Counter()]
subtract_neighbors  wrong   [Counter(), Counter({None: 1}), Counter(), Counter({None: 1})]
loop                correct [Counter(), Counter({None: 1}), Counter(), Counter()]
fun                 correct [Counter(), Counter({None: 1}), Counter(), Counter()]

Code doing the above checks (Try it online!):

def reference(A):
    a, b, c, d = A
    return [a, b-(a), c-(a+(b-a)), d-(a+(b-a)+(c-(a+(b-a)))) ]

def subtract_neighbors(A):
    a, b, c, d = A
    return [a, b-a, c-b, d-c]

def loop(A):
    B = []
    for a in A:
        if not B:
            b = sumB = a
        else:
            b = a - sumB
            sumB = sumB + b
        B.append(b)
    return B

def fun(A):
    B = A[:1]
    B += map(sub, A[1:], accumulate(B))
    return B

from collections import Counter
from itertools import accumulate
from operator import sub

funcs = [
    reference,
    subtract_neighbors,
    loop,
    fun,
]

tests = [
    [31, 41, 59,26],
    [1, 1, 1e-20, 1e-20],
    [Counter(), Counter([None])] * 2,
]

for A in tests:
    print('                        A =', A)
    for func in funcs:
        result = func(A)
        if func is reference:
            expect = result
            correctness = '       '
        else:
            correctness = 'correct' if result == expect else 'wrong  '
        print(f'{func.__name__:19}', correctness, result)
    print()

Solution 4:[4]

As suggest by the guys on the comments, this is the simplest and fastest solution:

A = [5, 9, 3, 8]
B = [x - y for x, y in zip(A, [0] + A)]

This outputs:

B
[5, 4, -6, 5]

Solution 5:[5]

You can also do it like this

A = [1,2,3,4]
B = [A[0]]+[A[i+1]-A[i] for i in range(len(A)-1)]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 ShadowRanger
Solution 3
Solution 4
Solution 5