Array index manipulation with multiple conditions - python

I want to change every index in an array that lies between two values.
I know:
a[a>10]=0
will change every index to 0 if the condition that the index itself is greater than 10 is true.
What I now want to achieve is to set every array index to 0 if two conditions are true, if for example the index is greater than 10 and less than 50, but
a[a>10 and a<50]=0
won't work.
I know I can to do this with a few lines of code but I feel like this wouldn't be the most elegant solution therefore my question is something like this possible in a similar elegant way as my simple example above, maybe also a one-liner?

You need parenthesis around each condition:
a[(i > 10) & (i < 50)]

Try this code:
a=np.arange(1,10)
a[(a>3) & (a<7)] = 0
# output: [1 2 3 0 0 0 7 8 9]

You can write this (click for a demo):
if i>10 and i<50: l[:] = [0]*len(l)
Let's break this down:
l[:] = value is a slicing statement. This is equivalent to l.__setitem__(slice(), value). For the built-in list object, leaving out the index to the left of the colon implies an index of zero, and leaving out the index to the right of the colon implies an index of the length of the array minus one. In other words, by leaving out both numbers on the sides of :, we implicitly set the entire list.
[0]*len(l) takes a list with a single element, 0, and multiplies it by the length of the list, creating a list of [0, 0, 0, 0, ... 0] as long as the original list.
If you want to do this in a single expression, you can write:
an empty parameter list (slice() uses None,None without arguments
| (which is equivalent to `l[:]`)
|
| ternary operator
v vv
l[slice(**([] if (i > 10 and i < 50) else 0,0))] = [0]*len(l)`
^ ^^
| kwargs argument unpacking
|
create a slice object -- l[x:y] is just syntactic sugar for l[slice(x, y)
This is syntactic sugar for setting [0]*len(l) to l[:] if the condition within holds true and [0]*len(l) to l[0:0] when the condition is false. l[0:0] is an empty range, so nothing changes.

Related

Dynamically add arguments in if condition in python

I am writing code for a tick-tac-toe game that uses grids. I am trying to check the condition where all the cells of a row match with each other, so that players can be declared winners. Also, I am using dynamic sizes of board (3 by 3, 4 by 4, 5 by 5....). So for this purpose, I am using if condition in a for loop. The problem is that I can't figure out a way to add arguments dynamically to the if statement. The if defined is static and I can't add the condition of the last cell of the row (if size increases from 3 by 3 to 4 by 4).
expected outcome:
3 by 3 grid:
count = 0
for i in range(dim):
if grid[count]=="X" and grid[1+ count]=="X" and grid[2+ count]=="X":
count1 = 0
print ("Player X win")
print ("-------------")
for i in range(dim):
print(grid[0 + count1 : dim + count1])
count1 =+ dim
print ("-------------")
count += dim
I am trying to loop for all rows to check the match.
What I want is to change the if grid[count]=="X" and grid[1+ count]=="X" and grid[2+ count]=="X": to incorporate the change of the grid size from
3 by 3 to
4 by 4
if grid[count]=="X" and grid[1+ count]=="X" and grid[2+ count]=="X" and grid[3+ count]=="X":
5 by 5
if grid[count]=="X" and grid[1+ count]=="X" and grid[2+ count]=="X" and grid[3+ count]=="X" and grid[4+ count]=="X":
is there a better way to do this?
You can use all() with a generator expression for this:
if all(grid[count + i] == "X" for i in range(dim)):
There's also a corresponding any() for or conditions
There are three pieces to how this works:
Comprehensions; we can write a list like [grid[count + i] == "X" for i in range(dim)] which will evaluate to something like [True, True, False, True]; it's basically a short-hand for a for loop that produces a list (or a dict or a set, if we use {}).
The all() (or any()) function; this is a simple function that takes a list and returns whether all (any) are true; all([True, True, False, True]) is False
Generator expressions; generators are basically single-use lists where the values are calculated only as they're needed. The syntax for a generator expression is either omitting the [], as here; or using round brackets (), where the syntax would otherwise be too confusing.
Because the values are calculated only as needed, this can be very valuable if we don't want to (or can't) store all the values in memory at once, or if we're likely to only look at the first few values. The all() function does that - it only checks as far as needed for the result to become clear. That's not much of an advantage when there's only going to be 3-5 values with a simple calculation, of course; here, it's mostly that it looks neater without the [].
One downside of generators is that they don't print well; you have to convert them to list to print out the items, and then you've used them up and can't use them again in the calculation. For debugging, the [grid[count + i] == "X" for i in range(dim)] form is more convenient.

Why does this reversed for loop miss the last item?

I want to reverse loop through a table and join the table items to make a string. This code works fine but it misses the last item of the table :
t = [0, 0, 2, 6, 14, 4, 7, 0]
for i in range(len(t) - 1, 0, -1):
res = str(t[i]) + res
return res
It prints 02614470 instead of 002614470.
I know if I change 0 to -1 in the loop parameter it would work properly but I want to understand why. It seems that when I want to use -1 as step, the middle parameter (0 in this case ) adds +1. So for example if I want the loop to stop at index 1 I have to write 0 in the parameter. Is that right?
That's my thought process but I don't know if it's correct.
The typical construction of a for loop with range() is:
t=[0,0,2,6,14,4,7,0]
for i in range(0,len(t)):
print(f"{i}, {t[i]}")
This makes sense to iterate through a list of items which starts at zero and is len() long. Since we start at zero, we have to stop at one less than the value returned for len(t), so range() is built to accommodate this most common case. As you noted in your case, since you are reversing this you would have to iterate through and use a -1 to capture the zero'th index in the list. Fortunately, you can use the following syntax to reverse the range, which leads to better readability:
t=[0,0,2,6,14,4,7,0]
for i in reversed(range(0,len(t))):
print(f"{i}, {t[i]}")
The second parameter in the range is a stop value so it is excluded from the generation.
for example, when you do range(10), Python processes this as range(0,10) and yields values 0,1,2,...,7,8,9 (i.e. not 10)
Going in reverse, if you want zero to be included, you have to set the stop value at the next lower value past zero (i.e. -1)
Other answers have explained that range does not include the end number. It always stops one short of the end, whether the range is forward or backward.
I'd like to add some more "Pythonic" ways to write the loop. As a rule of thumb try to avoid looping over list indices and instead loop over the items directly.
things = [...]
# Forward over items
for thing in things:
print(thing)
# Backward over items
for thing in reversed(things):
print(thing)
If you want the indices use enumerate. It attaches indices to each item so you can loop over both at the same time. No need for range or len.
# Forward over items, with indices
for i, thing in enumerate(things):
print(i, thing)
# Backward over items, with indices
for i, thing in reversed(enumerate(things)):
print(i, thing)

selecting sub-sequence confusion in python [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
The Python Slice Notation
I am confused with the way python subsequence selection works.
suppose i have this following code:
>>> t = 'hi'
>>> t[:3]
'hi'
>>> t[3:]
''
>>> print t[:3] + t[3:]
hi
>>> print t[3]
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
print t[3]
IndexError: string index out of range
please explain how this thing works in python
Subsequence, or slice, notation is forgiving. t[:3] will get you a slice of t from the beginning up to the end or the third element, whichever comes first, t[3:] will get you a slice of t from the third element if it exists through the end. Direct indexing such as t[3] is not forgiving; the indexed element must exist or else you get an exception. With slices, if the end index is out of range, you get the whole original list, if the start index is out of range, you get an empty list.
I always find it somewhat funny behavior of sequences that they allow slicing out of bounds. However, this is documented. Specifically in bullet point 4 which describes slicing of a sequence type:
The slice of s from i to j is defined as the sequence of items with index k such that i <= k < j. If i or j is greater than len(s), use len(s). If i is omitted or None, use 0. If j is omitted or None, use len(s). If i is greater than or equal to j, the slice is empty.
or bullet point 5 which describes slicing with the optional stride parameter:
The slice of s from i to j with step k is defined as the sequence of items with index x = i + n*k such that 0 <= n < (j-i)/k. In other words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j is reached (but never including j). If i or j is greater than len(s), use len(s). If i or j are omitted or None, they become “end” values (which end depends on the sign of k). Note, k cannot be zero. If k is None, it is treated like 1
Note that if you look at point 3 (which describes s[index]), there is no corresponding transform of out-of-bounds indices to in-bounds-indices.
t[start:stop] prints all elements x with start <= x < stop. When some elements do not exist it simply does not print them.
t[index] on the other hand gives an error if there is no element at given index.
In your example only t[0]='h' and t[1]='i' exist which explaines your results.
print t[3:] should return nothing instead of 'hi' which is also the case at my python interpreter.

Python Syntax / List Slicing Question: What does this syntax mean?

lines = file('info.csv','r').readlines()
counts = []
for i in xrange(4):
counts.append(fromstring(lines[i][:-2],sep=',')[0:-1])
If anyone can explain this code to me, it would be greatly appreciated. I can't seem to find more advanced examples on slicing--only very simple ones that don't explain this situation.
Thank you very much.
A slice takes the form o[start:stop:step], all of which are optional. start defaults to 0, the first index. stop defaults to len(o), the closed upper bound on the indicies of the list. step defaults to 1, including every value of the list.
If you specify a negative value, it represents an offset from the end of the list. For example, [-1] access the last element in a list, and -2 the second last.
If you enter a non-1 value for step, you will include different elements or include them in a different order. 2 would skip every other element. 3 would skip two out of every three. -1 would go backwards through the list.
[:-2]
Since start is omitted, it defaults to the beginning of the list. A stop of -2 indicates to exclude the last two elements. So o[:-2] slices the list to exclude the last two elements.
[0:-1]
The 0 here is redundant, because it's what start would have defaulted to anyway. This is the same as the other slice, except that it only excludes the last element.
From the Data model page of the Python 2.7 docs:
Sequences also support slicing: a[i:j] selects all items with index k such that i <= k < j. When used as an expression, a slice is a sequence of the same type. This implies that the index set is renumbered so that it starts at 0.
Some sequences also support “extended slicing” with a third “step” parameter: a[i:j:k] selects all items of a with index x where x = i + n*k, n >= 0 and i <= x < j.
The "what's new" section of the Python 2.3 documentation discusses them as well, when they were added to the language.
A good way to understand the slice syntax is to think of it as syntactic sugar for the equivalent for loop. For example:
L[a:b:c]
Is equivalent to (e.g., in C):
for(int i = a; i < b; i += c) {
// slice contains L[i]
}
Where a defaults to 0, b defaults to len(L), and c defaults to 1.
(And if c, the step, is a negative number, then the default values of a and b are reversed. This gives a sensible result for L[::-1]).
Then the only other thing you need to know is that, in Python, indexes "wrap around", so that L[-1] signifies the last item in the list, L[-2] is the second to last, and so forth.
If list is a list then list[-1] is the last element of the list, list[-2] is the element before it and so on.
Also, list[a:b] means the list with all elements in list at positions between a and b. If one of them is missing, it is assumed to mean the end of the list. Thus, list[2:] is the list of all elements starting from list[2]. And list[:-2] is the list of all elements from list[0] to list[-2].
In your code, the [0:-1] part it the same as [:-1].

How do I handle the following situation in Python?

I want to say
a[current] = value
rather than saying
a.append(value)
because I want to show that the current value is value. The former listing shows this better. I come from C, so I am a bit confused with python lists. In C I preallocate space, so a[current] would exist and contain junk before I assign it value. Can I do something similar in Python?
You can do something like
[0] * 10
which will result in
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
But your approach will probably not be very "pythonic". If switching to Python, you should also think about thinking in python. ;-)
When I first started using Python I ran into this problem all the time. You can do things like '[0]*10', but is inelegant, and will cause you problems if you try to do the same thing for lists of lists.
I finally solved the problem by realizing most of the time I just needed to reformulate the problem into something more pythonic. List comprehensions (as noted above) will almost always be the correct answer. If you are preallocating space, that means you are probably going to iterate over some other list, do some operation, then set an element in the new list to that value:
newList = [op(e) for e in oldList]
You can get fancier. If for example, you don't want all the elements, you can set up a filter:
newList = [op(e) for e in oldList if e < 5]
which will only put items that are less than 5 into the newList.
However, sometimes list comprehension isn't want you want. For example, if you're doing math-oriented coding, then numpy can come to the rescue:
myVector = numpy.zeros(10)
will create an array with 10 elements.
You can allocate a list of length n by using
my_list = [None] * n
Obviously, the list will be initialised rather than containing junk.
That said, note that often a list comprehension is a good replacement for a loop containing calls to list.append().
If you want to create a list with n elements initialized to zero (read "preallocate") in Python, use this:
somelist = [0] * n
Is this what you want?
If you don't like append, you can do things like
a = [None]*10
for i in range(10):
a[i] = something()
you might be interested also in python arrays.
I think that the most approximate syntax would be:
a.insert(current, value)
but if current isn't the last position in the array, insert will allocate some extra space and shift everything after current in one position. Don't know if this is the desired behavior. The following code is just like an append:
a.insert(len(a), value)
If you want to show that the current value is 'value', why don't you just use a variable for it?
a.append(value)
current_value = value
If you are maintaining a separate current variable to indicate where the next item will be inserted (that is, your line a[current] = value is followed immediately by current += 1 and you wish you could just write a[current++] = value), then you're writing C in Python and should probably stop. :-)
Otherwise you probably want to preallocate the list with the number of items you want it to contain, as others have shown. If you want a list that will automatically extend to fill in missing values, such that a[100] = value will work even if the list only has 50 items, this can be done with a custom __setitem__() method on a list subclass:
class expandinglist(list):
def __setitem__(self, index, value):
length = len(self)
if index < length:
list.__setitem__(self, index, value)
elif index = length: # you don't actually need this case, it's just a bit
self.append(value) # faster than the below for adding a single item
else:
self.extend(([0] * (index - length)) + [value])
lyst = expandinglist()
lyst[5] = 5
print lyst
>> [0, 0, 0, 0, 0, 5]

Categories

Resources