Freedom illusions
Freedom illusions

Reputation: 109

How to apply function to consecutive pairs of values from a list?

I have a dictionary

tickers = {'BTC': [200, 149, 98, 44], 'ETH': [200, 320, 405, 460]}

now and prev are elements of lists.

We compare this element and previous element in list.

For example in BTC:

149 with 200
98 with 149
44 with 98

# check state . describe state
def check_state(now, prev):
    state = None
    if now >= prev:
        if now <= 1.5 * prev:
                state = 0
    if now >= 1.5 * prev:
        state = 1
    if now < prev:
        if now * 1.5 >= prev:
               state = 2
    if now * 1.5 < prev:
        state = 3
    return state

I want to get new dictionary with tickers and states in every day. First day no state because first day didn't have previous.

tickers_state = {'BTC': [None, 3, 3, 3], 'ETH': [None, 1, 0, 0]}

where elements are state in every day of each ticker.

How can i do it?

Upvotes: 0

Views: 1358

Answers (2)

benvc
benvc

Reputation: 15120

You could compare each consecutive pair of values in your lists with a function that loops through the list and uses enumerate to enable access to the previous list item for comparison. Then use dict comprehension with your function to produce a mapped version of your original dict with the compared values.

In the example below, it loops through a slice of the list starting with the second list item so the previous element is accessed by the current value of i because the slice has essentially shifted the index values by 1.

tickers = {'BTC': [200, 149, 98, 44], 'ETH': [200, 320, 405, 460]}

def check_states(data):
    states = [None]
    for i, n in enumerate(data[1:]):
        state = None
        p = data[i]
        (low, high) = (p / 1.5, p * 1.5)
        if n >= p:
            state = 0 if n <= high else 1
        else:
            state = 2 if n >= low else 3           
        states.append(state)    
    return states

tickers_state = {k: check_states(v) for k, v in tickers.items()}

print(tickers_state)
# OUTPUT
# {'BTC': [None, 2, 3, 3], 'ETH': [None, 1, 0, 0]}

Upvotes: 1

Georgy
Georgy

Reputation: 13697

If you reverse the order of input arguments of check_state from def check_state(now, prev): to def check_state(prev, now): then the problem of applying a function to consecutive pairs of values of your lists becomes quite easy. I came up with the following function:

import itertools


def apply_pairwise(values,
                   function):
    """
    Applies function to consecutive pairs of elements from an input list
    """
    def pairwise(iterable):
        """
        s -> (s0,s1), (s1,s2), (s2,s3), ...
        """
        a, b = itertools.tee(iterable)
        next(b, None)
        return zip(a, b)

    yield from itertools.chain([None],
                               itertools.starmap(function, pairwise(values)))

Examples of usage:

>>> btc = [200, 149, 98, 44]
>>> list(apply_pairwise(btc, check_state))
[None, 2, 3, 3]
>>> eth = [200, 320, 405, 460]
>>> list(apply_pairwise(eth, check_state))
[None, 1, 0, 0]

If you can't reverse the inputs:

If it's impossible to change the order of inputs, we could adopt our function a bit:

import itertools


def apply_pairwise(values,
                   function,
                   *,
                   reverse=False):
    """
    Applies function to consecutive pairs of elements from an input list
    """
    def pairwise(iterable):
        """
        s -> (s0,s1), (s1,s2), (s2,s3), ...
        or -> (s1,s0), (s2,s1), (s3,s2), ... if reverse
        """
        a, b = itertools.tee(iterable)
        next(b, None)

        if reverse:
            return zip(b, a)
        return zip(a, b)

    yield from itertools.chain([None],
                               itertools.starmap(function, pairwise(values)))

and you could use it like this:

>>> btc = [200, 149, 98, 44]
>>> list(apply_pairwise(btc, check_state, reverse=True))
[None, 2, 3, 3]
>>> eth = [200, 320, 405, 460]
>>> list(apply_pairwise(eth, check_state, reverse=True))
[None, 1, 0, 0]

Explanation:

In order to get consecutive pairs of elements, we could use a helper function from recipes of itertools:

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

It works like this:

>>> list(pairwise(range(5)))
[(0, 1), (1, 2), (2, 3), (3, 4)]

Now for each pair of elements we would like to apply your check_state function. Here itertools.starmap can be useful. It works like this:

>>> list(itertools.starmap(pow, [(2, 3), (2, 10), (10, 3), (3, 4)]))
[8, 1024, 1000, 81]

The only thing left is to prepend the values yielded by starmap by None. As starmap makes an iterator, we could use itertools.chain to combine the first None with the rest of the elements.


P.S.: Applying this to values of your tickers dict should be easy enough. I will leave it to you.

Upvotes: 3

Related Questions