Roll over index if out of range (Python 3) - python

I'm trying to access the neighbouring elements (above, below, left and right) in a square 2D list. When the element I'm looking at is on an 'edge' of the 2D list, however, it will try to access a list index that doesn't exist. Here's the code I'm using:
surroundings = [
my_2D_array[currentY+1][currentX],
my_2D_array[currentY-1][currentX],
my_2D_array[currentY][currentX+1],
my_2D_array[currentY][currentX-1]
]
How do I get it to 'roll over', so in a list l with 3 items, instead of throwing an error accessing l[3], it would simply access l[0] instead?

The best way to perform a 'roll over', or 'wrap around' as I'd say, is to use modulus:
>>> x = [1, 2, 3]
>>> x[3 % len(x)]
1
>>> 3 % len(x) # 3 % 3 is 0
0
If you are 100% sure the length of the list is constant, simply hard-code the modulus right-hand-side value into your code:
x[index % 3]
This is because you could describe modulus as removing as many multiples of the RHS number from the LHS one, returning the vale left over. So, x % y returns the remainder after (floor) dividing x by y.

Related

np.insert() -1 as the position of an element

I am playing with the NumPy library and there is a behaviour that I do not understand:
I defined a simple vector Y:
Y = np.array([6,7,8],dtype = np.int16)
Then, I tried to add an additional element at the end of the array using the np.insert() method. I specified the position to be at -1:
Z = np.insert(Y,(-1),(19))
My element was inserted not on the last, but on the penultimate position:
Output
Could somebody explain my why this element was not inserted at the end of my array?
Negative indexes are calculated using len(l) + i (with l being the object and i being the negative index).
For example, with a list x = [1, 2, 3, 4] the index -2 is calculated as 4-2 = 2, so x[-2] returns 3
In this example, the index -1 is calculated as index 2
Therefore, 19 is inserted at index 2, the penultimate position. To fix this, use Z = np.insert(Y, len(Y), 19).
PS - this is the same with list.insert, not just with numpy arrays
Edit (suggested by Homer512): np.append may be easier than np.insert if you want to add to the end of an array: Z = np.append(Y, 19)

Slice all except first element, unless single element

I'd like to slice a numpy array to get all but the first item, unless there's only one element, in which case, I only want to select that element (i.e. don't slice).
Is there a way to do this without using an if-statement?
x = np.array([1,2,3,4,5])
y = np.array([1])
print(x[1:]) # works
print(y[1 or None:]) # doesn't work
I tried the above, but it didn't work.
A way to write that without a conditional is to use negative indexing with -len(arr) + 1:
>>> x = np.array([1,2,3,4,5])
>>> y = np.array([1])
>>> x[-len(x)+1:]
array([2, 3, 4, 5])
>>> y[-len(y)+1:]
array([1])
If the array has N elements where N > 1, slice becomes -N+1:. Since -N+1 < 0, it is effectively (N + (-N + 1)): === 1:, i.e, first one onwards.
Elsewhen N == 1, slice is 0:, i.e., take the first element onwards which is the only element.
Because of how slicing works, an empty array (i.e., N = 0 case) will result in an empty array too.
You can just write an if / else:
x[1 if len(x) > 1 else 0:]
array([2, 3, 4, 5])
y[1 if len(y) > 1 else 0:]
array([1])
Or:
y[int(len(y) > 1):]
array([1])
x[int(len(x) > 1):]
array([2, 3, 4, 5])
Just move None out of the brackets
x = [1,2,3,4,5]
y = [1]
print(x[1:])
print(y[1:] or None)
Use a ternary expression to make the logic clear but still be able to use it as a function argument or inside other expressions.
The ternary expression x if len(x) == 1 else x[1:] works and is very clear. And you can use it as a parameter in a function call, or in a larger expression.
E.g.:
>>> x = np.array([1,2,3,4,5])
>>> y = np.array([1])
>>> print(x if len(x) == 1 else x[1:])
[2 3 4 5]
>>> print(y if len(y) == 1 else y[1:])
[1]
Musings on other solutions
I'm not sure if you're looking for the most concise code possible to do this, or just for the ability to have the logic inside a single expression.
I don't recommend fancy slicing solutions with negative indexing, for the sake of legibility of your code. Think about future readers of your code, even yourself in a year or two.
Using this is a larger expression
In the comments, you mention you need a solution that can be incorporated into something like a comprehension. The ternary expression can be used as is within a comprehension. For example, this code works:
l = [np.array(range(i)) for i in range(5)]
l2 = [
x if len(x) == 1 else x[1:]
for x in l
]
I've added spacing to make the code easier to read, but it would also work as a one liner:
l2 = [x if len(x) == 1 else x[1:] for x in l]
EDIT note
Earlier, I thought you wanted the first element extracted from the list in the single-element case, i.e., x[0], but I believe you actually want that single-element list unsliced, i.e., x, so I've updated my answer accordingly.
Check the array size is greater than 1 if the case you can delete the first element from array and it will give new array without it.
print(np.delete(x, 0))
Now you can get a new array which will contain only remaining items other than first.

How do you replace a list of 5 0's with the first 5 even numbers? (python)

even_numbers = [0] * 5
for i in range(1,11):
if i%2 ==0:
even_numbers[i]=i
I am getting the error 'list assignment index out of range', although i'm not quite sure what that means.
"List index out of range" basically means that you are trying to access an array index that doesn't exist.
In your code, your loop goes up to 10, and you are trying to assign a number to even_numbers[10], while your array only has 5 elements. Basically you're telling your program "give me the 10th element of this 5-element list", which does not make much sense. Your indices in this case only go from 0 to 4.
To fix your issue, try storing the current index in a variable:
even_numbers = [0] * 5
current_index = 0
for i in range(1,11):
if i%2 ==0:
even_numbers[current_index]=i
current_index += 1
Edit: Alternatively, why not loop through even elements only, using the step argument of the range() function?
even_numbers = [0] * 5
current_index = 0
#will increment 'i' 2 by 2, i.e. i will be 2, then 4, then 6, ...
for i in range(2,11, 2):
even_numbers[i/2]=i
current_index += 1
The message means that you are trying to access an element of the list which does not exist.
The list only has five elements with indices 0 to 4. Trying to assign a value to even_numbers[i], you will try to assign a value to all elements of the list in the range of 1 to 10. That's why you should change the indexing to even_numbers[i/2]
The simplest solution is to generate the list of even numbers in the first place.
even_numbers = list(range(1,11,2))
If, for some reason, you already have the list, it's doesn't really matter what the original contents are if you are just going to replace them.
for i, _ in enumerate(even_numbers):
even_numbers[i] = 2*i
or just even_numbers[:] = range(1, 11, 2)

Why last element is not compared in List using zip() in python?

I am using build-in function zip() to compare 2 element in List.
myList =[11,12,93,14,15,45,56,67,78,11]
z = 0;
final = 0 ;
for x, y in zip(myList, myList[1:]):
if x > y :
z = x
if (final<x):
final = x;
print final;
For this code I get valid answer as: 93
Now if I add another element in last index as 333
myList =[11,12,93,14,15,45,56,67,78,11,333]
z = 0;
final = 0 ;
for x, y in zip(myList, myList[1:]):
if x > y :
z = x
if (final<x):
final = x;
print final;
I get still get output as :93 , but the correct answer in 333.
Can someone explain the logic as where I am wrong.
zip stops at the end of its shortest argument. myList[1:] is always one shorter than myList (provided the list isn't empty), and so the last pair you get from zip is x,y = 11,333:
>>> myList =[11,12,93,14,15,45,56,67,78,11,333]
>>> pairs = zip(myList, myList[1:])
>>> list(pairs)[-1]
(11, 333)
But you only ever assign final and z to the current value of x, which can never be the last element. You could add a phantom element to the end of the list that can't trigger the conditions: you're using mixed greater and lesser comparisons, so your best option is the special value "not a number" float('nan'), which is neither bigger nor smaller than any number (all comparisons return False). For the special case of pairs, you can put the dummy element in like this since the second argument is known to be exactly one element shorter than the longer one:
>>> pairs = zip(myList, myList[1:]+[float('nan')])
>>> list(pairs)[-1]
(333, nan)
But for a more general solution, you will want to use zip_longest from itertools, which effectively pads the end of the shorter arguments like this until they are the length of the longest one:
>>> pairs = it.zip_longest(myList, myList[1:], fillvalue=float('nan'))
>>> list(pairs)[-1]
(333, nan)
Take a look at itertools.izip_longest, as you are attempting to zip together iterables that are different in length. izip_longest will allow you to add a fill value for the iterables that are shorter than the longest one.
The reason this happens is because zip has length equal to the shortest sequence. Note that in the first case your list is of even length (so all pairs are returned), but in the second, it's odd, so you don't get the last one. If you wanted different behavior, take a look at itertools.izip_longest.

Setting variable to only equal even numbers, and the other to odds

I'm having trouble trying to split a list into even and odd numbers with the variables odd and even representing their respective numbers.
The professor noted that this line of code:
odd, even = foo([1,2,3,4,5,6], lambda x : x % 2 == 0)
Should split the numbers into odd and even. How do I do something like this? I know how to filter between odd and even numbers, but I'm unsure of how to set two variables in one line equal to their respective parts.
In your example foo is a function, and returns a pair of variables. For example:
def foo():
a = 1
b = 2
return (a, b)
x, y = foo() # x is now '1', and y is now '2'
So you need to create a function that iterates over the input list, and assigns the elements to either an odd list or an even list. Then return both of these lists, as the above example.
Return a tuple containing the odd values and the even values. The nearest your function could go it is:
>>> def foo(l,f):
... even = filter(f,l)
... odd = [i for i in l if i not in even]
... return odd,even
...
>>> odd, even = foo([1,2,3,4,5,6], lambda x : x % 2 == 0)
>>> odd
[1, 3, 5]
>>> even
[2, 4, 6]
This assignment is known as Tuple Unpacking. In this way when you return comma separated variables and the same number of variables on the left hand side, each of the variables on the left hand side will be respectively assigned to those on the right hand side.

Categories