Why does list index -0 form an empty list? - python

Imagine a list:
a = [1, 2, 3, 4, 5, 6, 7, 8]
where you simply want to iterate and print slices from this list by:
for i in range(len(a)-2):
print(a[2: -i])
This yields:
[]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
Here, I note that specifying a list index as a[2: -0] returns an empty list.
I would have thought this would have returned all the numbers from index 2 upwards.
Is there a simple explanation for this behaviour?
Returning to the example where the desired result would be:
[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
The introduction of an if statement seems like a clumsy way to treat this problem. Such as:
for i in range(len(a)-2):
if i == 0:
print(a[2:])
else:
print(a[2:-i])
Is there a better way?

for i in range(len(a)-2):
print(a[2: -i or None])
This can make it look neat. But as suggested by #Akshat2249 a[2:0] should return nothing.

There are a few ways to work around the issue, that -0 is 0, which puts it at the wrong end of the list for your intended slicing.
One option is as Vishal Dhawan has proposed, to substitute None for the -0 value. That works because None in a slice means "keep slicing until the end of the list".
Another option would be to do the math yourself for the indexes, rather than using negative indexes to count from the end. You'd want a[2:len(a)-i].
The best option is to change the range you're looping over so that it directly gives you the slice-end index you want, without any additional math being needed. Danil Melnikov's answer almost does this, but it has some errors with its range call, so it produces the wrong output. Try:
for i in range(len(a), 2, -1):
print(a[2: i])

a = [1, 2, 3, 4, 5, 6, 7, 8]
for i in range(len(a)-1, 0, -1):
print(a[2: i])
Would give:
[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
When you work with list slice it has logic like start from 2 and reach 0, but if start point > finish point the loop is over, we can specify the step, -1 or +1 [2:0:-1]

Related

Checking if a value is in a list of lists always returns False [duplicate]

This question already has answers here:
Best way to check if an item is present in a list of lists? [duplicate]
(5 answers)
Closed 2 years ago.
Currently working on a small sudoku solver, i use the boolean
if 0 in BOARD_EASY:
do the thing
having board as this value for testing purposes:
BOARD_EASY =[
[7,2,5,8,1,3,6,4,9],
[4,8,6,9,2,7,1,5,3],
[3,1,9,6,5,4,8,7,2],
[5,6,4,2,7,1,3,9,8],
[8,9,1,5,3,9,4,2,7],
[2,7,3,4,8,9,5,6,1],
[6,3,8,7,9,5,2,1,4],
[9,4,2,1,6,8,7,3,5],
[0,0,7,3,4,2,9,8,6]
]
for some reasons the boolean return false, while there is still 0's in the board, am i missing something?
It doesn't seems to check like the the first row of board_easy (i tried by removing the last digit for exemple)
Edit : Allowed me to finish my project, thank y'all ! (https://github.com/Lem0nRavioli/sudoku_solver)
I believe the in operator doesn't check nested values, just first level.
So it's looking through your list and it's only seeing other lists. See below.
BOARD_EASY =[
[7,2,5,8,1,3,6,4,9], #-> list
[4,8,6,9,2,7,1,5,3], #-> list
[3,1,9,6,5,4,8,7,2], #-> list
[5,6,4,2,7,1,3,9,8], #-> list
[8,9,1,5,3,9,4,2,7], #-> list
[2,7,3,4,8,9,5,6,1], #-> list
[6,3,8,7,9,5,2,1,4], #-> list
[9,4,2,1,6,8,7,3,5], #-> list
[0,0,7,3,4,2,9,8,6] #-> list
]
So the correct solution would be to loop through the list of lists BOARD_EASY then loop through the contents of each list as such:
for nested_list in BOARD_EASY:
for items in nested_list:
if 0 in items:
print("Found!")
I know you already have a response for this, I wanted to share an option that minimize the iteration using any
if any(0 in elem for elem in BOARD_EASY):
print ('0 is found in BOARD_EASY')
else:
print ('0 is NOT found in BOARD_EASY')
The importance of any function is that it short-circuits the execution as soon as it finds the value. So it does not iterate through the full list.
Looking at your code, your if statement if 0 in BOARD_EASY: will result in if (0 == BOARD_EASY[0]) or (0 == BOARD_EASY[1]) ...... The list has many elements so the compare is not correct. You need to look for each item in the list and compare the elements in the inner list. With the code above (with any function), it does the trick.
Per the docs for the in operator:
The operators in and not in test for membership. x in s evaluates to True if x is a member of s, and False otherwise.
0 is not a member of BOARD_EASY. The list [0,0,7,3,4,2,9,8,6] is.
So you could do something like:
any(0 in elem for elem in BOARD_EASY)
You could use numpy.flatten:
BOARD_EASY = np.array(BOARD_EASY).flatten()
print("do the thing") if 0 in BOARD_EASY else False
do the thing
I see all answers are iterating through all elements, or copying all of the data, or implementing 3 lines of code to do it, so here is a fast and simple solution:
if 0 in itertools.chain(*BOARD_EASY):
do the thing
What chain does is it iterates through each item in BOARD_EASY, as if it was iterating through just one list.
BOARD_EASY is a list of lists. BOARD_EASY does not contain a 0...it contains 9 lists.
Here, look:
BOARD_EASY = [
[7, 2, 5, 8, 1, 3, 6, 4, 9],
[4, 8, 6, 9, 2, 7, 1, 5, 3],
[3, 1, 9, 6, 5, 4, 8, 7, 2],
[5, 6, 4, 2, 7, 1, 3, 9, 8],
[8, 9, 1, 5, 3, 9, 4, 2, 7],
[2, 7, 3, 4, 8, 9, 5, 6, 1],
[6, 3, 8, 7, 9, 5, 2, 1, 4],
[9, 4, 2, 1, 6, 8, 7, 3, 5],
[0, 0, 7, 3, 4, 2, 9, 8, 6]
]
for i, x in enumerate(BOARD_EASY):
print(i+1, x)
Result:
1 [7, 2, 5, 8, 1, 3, 6, 4, 9]
2 [4, 8, 6, 9, 2, 7, 1, 5, 3]
3 [3, 1, 9, 6, 5, 4, 8, 7, 2]
4 [5, 6, 4, 2, 7, 1, 3, 9, 8]
5 [8, 9, 1, 5, 3, 9, 4, 2, 7]
6 [2, 7, 3, 4, 8, 9, 5, 6, 1]
7 [6, 3, 8, 7, 9, 5, 2, 1, 4]
8 [9, 4, 2, 1, 6, 8, 7, 3, 5]
9 [0, 0, 7, 3, 4, 2, 9, 8, 6]
The equivalent code you're looking for checks each list in sequence:
for i, l in enumerate(BOARD_EASY):
if 0 in l:
print("There's a zero in row " + str(i + 1))
Result:
There's a zero in row 9
Board_easy is a list of lists so the first element is [7,2,5...] not 7. You will need a nested for loop to iterate over each dimension
Nested for loops are unnecessary if you are only checking for membership of the element in one of your indices:
for count, i in enumerate(BOARD_EASY):
if 0 in i:
print("Found it in index", count)
Thanks for all the answers, allowed me to understand what was going on with the 'in' operator.
Ended up using a line related to the one of David Erickson:
# start of the script
BOARD_EASY = np.array(BOARD_EASY)
#
#
if 0 in np.concatenate(BOARD_EASY):
do the thing

How to slice a list from an element n to the end inside a loop in python?

I'd like a minor clarification of the topic at:
How to slice a list from an element n to the end in python?
I want to slice a list inside a loop, so that the last iteration of the loop includes the last element of the list. The only way I've found to do this so far (based on the answers in the other thread) is as follows:
>>> x = list(range(1,11))
>>> for i in range(0,4):
... x[i:-3+i if -3+i != 0 else None]
The values of x that I expect in each iteration are:
[1, 2, 3, 4, 5, 6, 7]
[2, 3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8, 9]
[4, 5, 6, 7, 8, 9, 10]
It works, but is my solution really the most Python-esque way to accomplish this?
You're going to a lot of trouble for a straightforward task.
How about
x[i:i+7]
This gives the same output, and is much easier to read. The difficulty in your original approach seems to be working around the problem of the list end being 1 past element -1, but you can't designate this as element 0. You can fix the problem by using positive indexing alone.
I'm not exactly sure about what your question is, but I think that using a simpler approach would work as well:
x = list(range(1, 11))
slice_length = 7
number_of_iterations = 4
# All you had to do was to first iterate
for i in range(number_of_iterations):
# Then slice the list from i to i+7 in your case
print(x[i:i+slice_length])
# Output:
# [1, 2, 3, 4, 5, 6, 7]
# [2, 3, 4, 5, 6, 7, 8]
# [3, 4, 5, 6, 7, 8, 9]
# [4, 5, 6, 7, 8, 9, 10]

How could a specified number of elements be extracted from a list with an even distribution?

The solution here shows how one could extract a certain percentage of elements from a list with an even distribution. How could a specified number of elements be extracted from a list with an even distribution?
One way to do this is with a recursive function. This makes it quicker for me to write, but is not very pythonic, I guess. Anyway, it gets the job done:
>>> def select_elements(myList,num):
if len(myList)<=num: return myList #Might not be enough elements
if num==0: return []
if num==1: return [myList[int(round((len(myList)-1)/2))]]
return [myList[int(round((len(myList)-1)/(2*num)))]]+select_elements(myList[int(round((len(myList)-1)/(num))):],num-1)
>>> i = range(10)
>>> for j in range(11):
print(j,select_elements(i,j))
0 []
1 [4]
2 [2, 6]
3 [2, 5, 8]
4 [1, 3, 5, 8]
5 [1, 3, 5, 7, 8]
6 [1, 3, 4, 6, 7, 8]
7 [1, 2, 3, 4, 6, 7, 8]
8 [1, 2, 3, 4, 5, 6, 7, 8]
9 [0, 1, 2, 3, 4, 5, 6, 7, 8]
10 range(0, 10)
We could spend all day arguing about what the right distribution would be in some of these scenarios. It really depends what you want. The above could be improved somewhat, but I thought I'd get things going...

Multidimensional slicing in raw Python

Let's say I have an array defined as
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
and I wanted to get a vertical slice, returning [2, 5, 8]. Is there any way to do this with slicing syntax in straight Python? When I try to look up multidimensional slicing, the only results I tend to see are all talking about numpy, and using the syntax discussed there gives errors in raw Python.
I don't know of a direct way to get a vertical slice and this is probably more expensive than the list comprehension answer but still
z = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
list(zip(*z)[1])
[2, 5, 8]
Or this may be slightly less expensive
from itertools import izip, islice
list(*islice(izip(*z), 1, 2))
[2, 5, 8]
Sure. You could generally use a for loop. Or simply you could code like:
>>> test = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
>>> [row[1] for row in test]
[2, 5, 8]
This piece of code will generate a new list for a vertical slice. If you really need a slice operation, the following code would be useful:
>>> [item for sublist in test for item in sublist][1::len(test[0])]
[2, 5, 8]

Python list extend functionality using slices

I'm teaching myself Python ahead of starting a new job. Its a Django job, so I have to stick to 2.7. As such, I'm reading Beginning Python by Hetland and don't understand his example of using slices to replicate list.extend() functionality.
First, he shows the extend method by
a = [1, 2, 3]
b = [4, 5, 6]
a.extend(b)
produces [1, 2, 3, 4, 5, 6]
Next, he demonstrates extend by slicing via
a = [1, 2, 3]
b = [4, 5, 6]
a[len(a):] = b
which produces the exact same output as the first example.
How does this work? A has a length of 3, and the terminating slice index point is empty, signifying that it runs to the end of the list. How do the b values get added to a?
Python's slice-assignment syntax means "make this slice equal to this value, expanding or shrinking the list if necessary". To fully understand it you may want to try out some other slice values:
a = [1, 2, 3]
b = [4, 5, 6]
First, lets replace part of A with B:
a[1:2] = b
print(a) # prints [1, 4, 5, 6, 3]
Instead of replacing some values, you can add them by assigning to a zero-length slice:
a[1:1] = b
print(a) # prints [1, 4, 5, 6, 2, 3]
Any slice that is "out of bounds" instead simply addresses an empty area at one end of the list or the other (too large positive numbers will address the point just off the end while too large negative numbers will address the point just before the start):
a[200:300] = b
print(a) # prints [1, 2, 3, 4, 5, 6]
Your example code simply uses the most "accurate" out of bounds slice at the end of the list. I don't think that is code you'd use deliberately for extending, but it might be useful as an edge case that you don't need to handle with special logic.
It's simply an extension of normal indexing.
>>> L
[1, 2, 3, 4, 5]
>>> L[2] = 42
>>> L
[1, 2, 42, 4, 5]
The __setitem__() method detects that a slice is being used instead of a normal index and behaves appropriately.
a = [1, 2, 3]
b = [4, 5, 6]
a[len(a):] = b
means element in a from position len(a) are elements in b. Which means extending a with b.
For a demonstration, consider looking at a subclass of list:
from __future__ import print_function # so I can run on Py 3 and Py 2
class EdList(list):
def __setitem__(self,index,value):
print('setitem: index={}, value={}'.format(index,value))
list.__setitem__(self,index,value)
print(self)
def __setslice__(self,i,j,seq):
print('setslice: i:{}, j:{}, seq:{}'.format(i,j,seq))
self.__setitem__(slice(i,j),seq)
Running on Python 3:
>>> a=EdList(range(10))
>>> a[300000:]=[1,2,3]
setitem: index=slice(300000, None, None), value=[1, 2, 3]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
>>> a[1:1]=[4,5,6]
setitem: index=slice(1, 1, None), value=[4, 5, 6]
[0, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
Running on Python 2:
>>> a=EdList(range(10))
>>> a[300000:]=[1,2,3]
setslice: i:300000, j:9223372036854775807, seq:[1, 2, 3]
setitem: index=slice(300000, 9223372036854775807, None), value=[1, 2, 3]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
>>> a[1:1]=[4,5,6]
setslice: i:1, j:1, seq:[4, 5, 6]
setitem: index=slice(1, 1, None), value=[4, 5, 6]
[0, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
It is confusing when you are first learning it, but you will learn to love it I think.

Categories