Reputation: 1569
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.
Upvotes: 39
Views: 4354
Reputation: 42143
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]
Upvotes: 4
Reputation: 46859
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]
Upvotes: 7
Reputation: 311
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)
Upvotes: 1
Reputation: 11228
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)
[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)]
Upvotes: 1
Reputation: 46859
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]
Upvotes: 18
Reputation: 88236
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])
Upvotes: 7
Reputation: 555
Something like this might be more direct:
index = DEGREES.index(90)
print([DEGREES[i] for i in range(index, index-13, -1)])
Upvotes: 11
Reputation: 32512
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]
Upvotes: 1
Reputation: 59701
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]
Upvotes: 3
Reputation: 54223
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
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]
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.
Upvotes: 40
Reputation: 5774
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)
Upvotes: 1
Reputation: 459
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.
Upvotes: 1
Reputation: 5190
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.
Upvotes: 1
Reputation: 4434
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]
Upvotes: 1
Reputation: 1256
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:
Upvotes: 1
Reputation: 97
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])
Upvotes: 1