Slice a list based on an index and items behind it in Python

Question:

Say I have an array of degree values, like this:

DEGREES = [
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

I would pick an angle and then be able to bisect this hypothetical circle in order to make it easier to find the shortest route to the target direction.

Saying that, how can I pick a specific value, like 90, and then be able to find the previous 12 elements behind that, including the index wrapping around to the end?

So, taking that earlier value and applying to that list, I would get something like this:

[90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]

Using slice notation, I tried doing this:

index = DEGREES.index(90)
print(DEGREES[index-12:index]) # start 12 values back, stop at index

But this only prints an empty array.

Is there a way to slice a list so I can get the 12 previous values behind the index I’m using?

EDIT:

This turned out to be an XY Problem, my bad. Originally, I was trying to create a smooth rotation system in Pygame, with my attempts to calculate angles not working, I asked this question to solve a problem with yet another idea I was trying to implement. I ended up accepting the answer that helped me set up the smooth rotation system, but there are relevant answers to the original question below that.

Asked By: Mercury Platinum

||

Answers:

For these cases, a NumPy function that comes in handy is np.roll, which, as its name specifies, rolls the elements in the array, and as also as mentioned in the documentation:

Elements that roll beyond the last position are re-introduced at the first

Which is exactly what we need in order to roll at the back the first items in the list up to the index where 90 appears.

So one approach could be to use the index where 90 appears using the index list method, and shift the array up to -k positions, k being the given index. Then we can just slice the list and take its last n elements reversed:

import numpy as np

l = [
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

def roll_n_reversed(l, i, n):
    return np.roll(l, -l.index(i)-1)[:-(n+1):-1]

roll_n_reversed(l, 90, 13)

Which yields the expected output:

array([ 90,  75,  60,  45,  30,  15,   0, 345, 330, 315, 300, 285, 270])
Answered By: yatu

I think itertools.chain might be useful here:

from itertools import chain

DEGREES = [
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345
]

def get_list_of_degrees(degree, resulting_list_length):
    index = DEGREES.index(degree)
    lower_index = index - (resulting_list_length)
    if index >= resulting_list_length:
        result = DEGREES[lower_index: index]  # start 12 values back, stop at index
    else:
        result = list(chain(DEGREES[lower_index:], DEGREES[:index])) # start 12 values back, stop at index
    return result

my_degrees = get_list_of_degrees(90, 12)
print(my_degrees)

Yields:

[270, 285, 300, 315, 330, 345, 0, 15, 30, 45, 60, 75]

Which is what you specified, just backwards

Perhaps a more straightforward and scaleable/alterable method would be to generate the angles on the fly without a DEGREES list. Something like:

def get_angles(start_angle=90, increment=-15, return_array_size=12):
    angles = [i for i in range(start_angle + increment, start_angle + (return_array_size*increment) + increment, increment)]
    for index in range(len(angles)):
        while angles[index] < 0:
            angles[index] += 360
    return angles

print(get_angles())

Returns:

[75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]

While allowing you the flexibility to return only 5 angles easily, or go in step sizes of 2 degrees, etc.. For instance

print(get_angles(increment=-2))

Now returns:

[88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66]

With very minimal change done to your code (otherwise you would have to generate a new DEGREES array to accomplish this)

Answered By: Reedinationer

The reason you got empty list is because you simply don’t have 12 items prior to value of 90.

What you need is a statement to handle this exception:

index = DEGREES.index(90)
if index >= 12:
    print(DEGREES[index-12:index])
else: 
    print(DEGREES[:index])
Answered By: Verse He

Or you could use a deque:

from collections import deque
from itertools import islice

dq = deque(reversed((0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345)))

index = dq.index(90)
dq.rotate(-index)
res = list(islice(dq, 13))
# [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]

You could use that as a function:

def f(i):
    dq.rotate(-dq.index(i))
    return list(islice(dq, 13))

#  f(90) = [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]
Answered By: hiro protagonist

By list slicing:

DEGREES = [
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

value = 90
index = DEGREES.index(value)



result = DEGREES[:index+1][::-1] + DEGREES[index+1:][::-1]
result = result[:13]
print(result)

Output

[90, 75, 60, 45, 30, 15, 0, 345, 330,
 315, 300, 285, 270]

or

RES= [ DEGREES[i] for i in range(index,index-12-1,-1)]
Answered By: sahasrara62

Something like this might be more direct:

index = DEGREES.index(90)
print([DEGREES[i] for i in range(index, index-13, -1)])
Answered By: John Furtado

You cannot do that with one slice unfortunately. You can either concatenate the pieces, which can be a bit awkward:

DEGREES = [
    0, 15, 30, 45, 60, 
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

index = DEGREES.index(90)
result = DEGREES[index:index - 12:-1] if index >= 12 else (DEGREES[index::-1] + DEGREES[:index - 12:-1])
print(result)
# [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285]

Or just use a list comprehension:

DEGREES = [
    0, 15, 30, 45, 60, 
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

index = DEGREES.index(90)
result = [DEGREES[i] for i in range(index, index - 12, -1)]
print(result)
# [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285]
Answered By: jdehesa

I think you need to do some arithmetic.

index = DEGREES.index(90) + 1
offset = 12
start = index - offset
length = len(DEGREES)
print(
    list(reversed(DEGREES[max(0, start):index])) + 
    (list(reversed(DEGREES[length + start - 1 :length])))
     if start < 0
     else [])
)

Alternatively:

Answered By: Max Gasner

I would suggest you to try itertools.cycle() for any number of previous values.

Just reverse the list and try cycle().

import itertools

degrees = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345]
n=12
degrees.reverse()
ind = degrees.index(90)
degrees = degrees[ind:]+degrees[:ind]
rev_cycle = itertools.cycle(degrees)
for i in range(n+1):
    print(next(rev_cycle))

This is efficient as it is using generators.

Answered By: Underoos

In your example, the elements you wish to print out are DEGREES[-6:6]. You might want to add conditionals to take care of starting indexes that end up looping back around. Something like this:

DEGREES = [
    0, 15, 30, 45, 60, 
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

index = DEGREES.index(90)
start_idx = index - 12
if start_idx < 0:
    print(DEGREES[start_idx:] + DEGREES[:index + 1])
else:
    print(DEGREES[start_idx:index + 1])

this should return the following:

[270, 285, 300, 315, 330, 345, 0, 15, 30, 45, 60, 75, 90]

which is your solution, but reversed.

Answered By: Anshuman Dikhit
DEGREES = [
    0, 15, 30, 45, 60, 
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]


index = DEGREES.index(90)

subFront = DEGREES[:index + 1][-12:]
subFront.reverse()

remainLen = 12 - len(subFront) + 1
if remainLen > 0:
    subBack = DEGREES[-remainLen:]
    subBack.reverse()
    subFront = subFront + subBack
print(subFront)
[90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]
Answered By: Yang Yu

An itertools (cycle and islice) based solution:

from itertools import cycle, islice

DEGREES = cycle(reversed((
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345)))

next(item for item in DEGREES if item == 90)  # advance to next 90
res = [90] + list(islice(DEGREES, 12))
# [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]

You can pack that into a one-liner function:

def f(i):
    return [next(d for d in DEGREES if d == i), *islice(DEGREES, 12)]

#  f(90) = [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]

Or even using dropwhile (as mentioned in the comments):

from itertools import cycle, islice, dropwhile

def f(i):
    return list(islice(dropwhile(lambda d: d != i, DEGREES), 13))

If your list is exactly as you printed above, you could also generate the slices on the fly using range:

def f(i, d=15, n=13):
    return [deg % 360 for deg in range(i, i-n*d, -d)]

# f(90) = [90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]
Answered By: hiro protagonist

Or

import numpy as np

DEGREES = [
    0, 15, 30, 45, 60,
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]
idx = DEGREES.index(90)
new_list = DEGREES[::-1]
newList = np.roll(new_list, idx+1)
print(newList)
Answered By: pdrersin

Arithmetic with angles

Your goal isn’t to slice, concatenate or reverse lists. Your goal is to do basic arithmetic with degrees and keep the results between 0 and 359. For this, you really should use the modulo operator %:

>>> 90 % 360
90
>>> 390 % 360
30
>>> -60 % 360
300
>>> 360 % 360
0

Back to the question

If you only want to use this slicing for degrees with a constant increment, you could generate the desired list directly:

>>> STEP = 15
>>> list(range(0, 360, STEP))
[0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345]
>>> def previous_degrees(start, n, step=STEP):
...     return [(start - i * step) % 360 for i in range(n + 1)]
... 
>>> previous_degrees(90, 12)
[90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]
>>> previous_degrees(90, 12, 30)
[90, 60, 30, 0, 330, 300, 270, 240, 210, 180, 150, 120, 90]
>>> previous_degrees(90, 6, 45)
[90, 45, 0, 315, 270, 225, 180]

Your real question

You wrote in a comment:

This array of degrees is designed to work with a smooth rotation
system that I’m trying to create in pygame. Normally I would just find
the difference between the current direction and the target direction
and increment from there, but since the rotation rolls over at zero I
have to hardcode the values to make sure that it will always go the
shortest route possible.

From two angles, you need to determine if you should turn clockwise or anticlockwise. You can use modulo again to make sure that the rotation will be between -180° and 179°:

def shortest_rotation(start_angle, end_angle):
    return (end_angle - start_angle + 180) % 360 - 180

Here’s an example:

>>> shortest_rotation(0, 90)
90
>>> shortest_rotation(90, 0)
-90
>>> shortest_rotation(90, 90)
0
>>> shortest_rotation(90, 330)
-120
>>> shortest_rotation(0, 180)
-180
>>> shortest_rotation(0, 181)
-179
>>> shortest_rotation(0, 179)
179
>>> shortest_rotation(10, 350)
-20

You can now create a list of angles, turning in the shortest direction:

def rotation_steps(start_angle, end_angle, n):
    increment = shortest_rotation(start_angle, end_angle) / n
    return [(start_angle + i * increment) % 360 for i in range(n + 1)]

As an example:

>>> rotation_steps(90, 270, 12)
[90.0, 75.0, 60.0, 45.0, 30.0, 15.0, 0.0, 345.0, 330.0, 315.0, 300.0, 285.0, 270.0]
>>> rotation_steps(10, 350, 2)
[10.0, 0.0, 350.0]

The list uses float in order to avoid missing the end_angle if increment isn’t an integer.

Answered By: Eric Duminil

You can use this:

previous12 = [DEGREES[p-i] for p in [DEGREES.index(90)] for i in range(13)]

or this:

previous12 = (DEGREES+DEGREES[:DEGREES.index(90)+1])[:-14:-1]
Answered By: Alain T.

I have this handy function that implements wrapping slicing. While your use case might be better solved by directly computing the angle values as other answers have already shown. This might do the trick:

def wrapping_slice(lst, *args):
    return [lst[i%len(lst)] for i in range(*args)]

Example:

DEGREES = [
    0, 15, 30, 45, 60, 
    75, 90, 105, 120,
    135, 150, 165, 180,
    195, 210, 225, 240,
    255, 270, 285, 300,
    315, 330, 345,
]

start = DEGREES.index(90)

print(wrapping_slice(DEGREES, start, start-13, -1))

The output is:

$ python test.py
[90, 75, 60, 45, 30, 15, 0, 345, 330, 315, 300, 285, 270]
Answered By: moooeeeep
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.