List Range Refinement - python

I have been using the a[0:2] format for ranges but it has been bothering me that if I have a = range(0, 5) I get a[0, 1, 2, 3, 4] but if I use a[0:-1] I get a[0, 1, 2, 3].
I know if I use a[0:] I get the full range, but if I want to have the end of the range defined by a variable (example: c = -1 then a[0,c]) there is no way for me to get the full range without using a conditional statement (for instance: if c == -1: c = None).
Is there some nice format that I could use to be able to access the whole range while using variables as the limits? Or am I stuck needing a conditional statement?
Thanks.
Edit: It appears I have two options available, I can either set the variable to None conditionally or I can set the variable so that the last term is set at len(a). I am not 100% sure which way I am going to go with yet, but thank you all for your responses.

Just assign None to c:
c = None
a[2:c]
It works as you want. Actually that's how slices (not ranges) are created.
They are actually ordinary Python objects. You can even use them inside [].
a = [0, 1, 2, 3]
s = slice(2, None)
a[s] # equal to a[2:]

a[0:] is just syntactic sugar for a[0:len(a)]
Thus
c = len(a)
a[0:c] #a[:c], a[:], a[0:] all work as well
Gives you the full range.

You can use slice:
endLimit = int(raw_input("what is the limit?"))
if endLimit == 0:
endLimit = None
range(5)[slice(0,endLimit)]
slice is in fact the object the represents the [start:end:jumps] part of the range(5)[start:end:jumps].

You can use the ternary operator:
a[0:c if c>0 else None]
But that's pretty ugly.
I think the better question is "Why do you want to use -1 to get the full slice?". Python already uses -1 to get up to (but not including) the final element. Changing that behavior just leads to unexpected results for people familiar with the language. Just document the fact that if the user of this function wants to get the full slice, they should pass None.

EDIT:
Now that I understand exactly what OP is looking for, this'd be the way to do it:
a[0:c if c != 0 else len(a)]
Which uses a ternary, or if you really really really don't want to use a conditional, I am pretty certain this does exactly what you want and gives you a full slice on c = 0:
a[0:((c-1) % len(a)) + 1]
---------------------- Old Post ---------------------------
Well, I may be missing something, but you can go arbitrarily high. If you do:
a = range(5)
a[0:200]
You get:
[0, 1, 2, 3, 4]
So if c is just growing or doing it's own thing the full range will be given every time c ≥ len(a).

Related

No index error raised for range indexing. How to raise it? [duplicate]

Why doesn't 'example'[999:9999] result in error? Since 'example'[9] does, what is the motivation behind it?
From this behavior I can assume that 'example'[3] is, essentially/internally, not the same as 'example'[3:4], even though both result in the same 'm' string.
You're correct! 'example'[3:4] and 'example'[3] are fundamentally different, and slicing outside the bounds of a sequence (at least for built-ins) doesn't cause an error.
It might be surprising at first, but it makes sense when you think about it. Indexing returns a single item, but slicing returns a subsequence of items. So when you try to index a nonexistent value, there's nothing to return. But when you slice a sequence outside of bounds, you can still return an empty sequence.
Part of what's confusing here is that strings behave a little differently from lists. Look what happens when you do the same thing to a list:
>>> [0, 1, 2, 3, 4, 5][3]
3
>>> [0, 1, 2, 3, 4, 5][3:4]
[3]
Here the difference is obvious. In the case of strings, the results appear to be identical because in Python, there's no such thing as an individual character outside of a string. A single character is just a 1-character string.
(For the exact semantics of slicing outside the range of a sequence, see mgilson's answer.)
For the sake of adding an answer that points to a robust section in the documentation:
Given a slice expression like s[i:j:k],
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). When k is positive, i and j are reduced to len(s) if they are greater
if you write s[999:9999], python is returning s[len(s):len(s)] since len(s) < 999 and your step is positive (1 -- the default).
Slicing is not bounds-checked by the built-in types. And although both of your examples appear to have the same result, they work differently; try them with a list instead.

What is the most pythonic way to apply function to some items of a list?

Let's take this code that prints all positive integer of a list:
l = [1, -1, 1, 0, 2]
for i in l:
if i > 0:
print(i)
I can do it with a list comprehension but I guess this has the disadvantage to create a new useless list:
[print(i) for i in l if i > 0]
So my question: is there a more pythonic way to write this?
The most Pythonic way to apply a function to some subset of a list of elements is to use a for loop, just as you already have.
Within that for loop, there is an argument to be made for filtering the list before assigning any value to i; whether that constitutes an improvement is usually a matter of opinion and will depend on the context.
for i in filter(lambda x: x > 0, l):
print(i)
In this case, I think it's worse. But sometimes you have a predicate at hand, and filtering can be syntactically lighter. Compare
for i in some_list_of_strings:
if i.isdigit():
print(i)
with
for i in filter(str.isdigit, some_list_of_strings):
print(i)
The plain for-loop is perfectly Pythonic. You want to loop over the elements of the list, select the ones that are greater than zero, and print them, and that's exactly what it does - no more, no less.
The list comprehension is not Pythonic, mostly for the reason you gave: it creates a new useless list. Even if you were going to use the list, using a list comprehension for side effects is still bad practice.
I suppose that theoretically you could contrive to use a generator comprehension in order to avoid creating a large list in memory:
for _ in (print(i) for i in l if i > 0): pass
(maybe also using some function that does the consuming of values from the generator, so that any loop is hidden away inside that function).
However, not only is this less readable than the explicit for loop, the plain for loop is also quicker.
import time
l = [1, -1, 1, 0, 2]
# we don't really want lots of output for this timing test
def dont_really_print(i):
return i
t1 = time.time()
for x in range(1000000):
for i in l:
if i > 0:
dont_really_print(i)
t2 = time.time()
for x in range(1000000):
for _ in (dont_really_print(i) for i in l if i > 0):
pass
t3 = time.time()
print(f"generator comprehension {t3 - t2:.3f} "
f"explicit loop {t2 - t1:.3f}")
gives:
generator comprehension 0.629 explicit loop 0.423
Generally, whether Python or any other language, what you want to do is known as a "map."
Print is a weird function to map, so here's another function for better demonstration purposes.
def add_one(num):
return num + 1
foo = [1, 2, 3, 4]
new_list = map(add_one, foo)
print(list(new_list))
You can typically map in parallel, sometimes asynchronously, and a good number of parallel processing paradigms (including Python's multiprocessing) use map as a fundamental operation for using multiple cores to implement a function.

Why does it not remove all zeroes from the list

I haven't coded in Python for a long time and sometimes I'm quite confused.
When I have an array, like eg.: arr = [0, 1, 0, 3, 12]
and say:
for i in arr:
if i == 0:
arr.remove(i)
it is removing the two zeroes the way I want.
But if the array is something like: arr = [0, 0, 1], with the above method, one 0 will remain.
So could someone explain me why the behavior is like that? I don't find an explanation for this.
Better try this:
arr = [n for n in arr if n != 0]
This uses a list comprehension, it's a lot safer than what you're doing: removing the elements at the same time you're iterating is a bad idea, and you'll experience problems such as elements not being removed.
This is because the list size is reduced and the iterator traversing it will find less elements than it was expecting when the iteration began.
I think I found why your method doesn't work. The problem comes from the way you iterate.
In your example, your function seems to work for arr = [0,1,0,3,12] but not on your second array arr2 = [0,0,2] and returns [0,2]. One interesting thing to investigate then, is the fact that in your second example, you have two consecutive zeros.
Take a look at this code and try to execute it :
for i in arr:
print('i = '+str(i))
if(i == 0):
arr.remove(i)
With your first array, you noticed that your output is the one you expected but that was lucky. As a matter of fact, if you run the code above, you would see that it prints in your console :
> i = 0
> i = 0
> i = 12
So, actually, this means that your remove statement changes the array you iterate on. After a deletion, you skip an element in your array.
This means you should prefer another way, like the ones suggested in comments.
Hope this helps
you can filter out your zeros with the built-in function filter:
arr = list(filter(None, arr))
you have to pay attention if you use filter function with None as first parameter, this will apply bool over your items if you have elements like None, 0 or the empty string '' the result will be the same, False and all these elements will be filtered out, for safety reasons you may use:
arr = list(filter(lambda x: x != 0 , arr))

Indexing vs Slicing of empty strings [duplicate]

Why doesn't 'example'[999:9999] result in error? Since 'example'[9] does, what is the motivation behind it?
From this behavior I can assume that 'example'[3] is, essentially/internally, not the same as 'example'[3:4], even though both result in the same 'm' string.
You're correct! 'example'[3:4] and 'example'[3] are fundamentally different, and slicing outside the bounds of a sequence (at least for built-ins) doesn't cause an error.
It might be surprising at first, but it makes sense when you think about it. Indexing returns a single item, but slicing returns a subsequence of items. So when you try to index a nonexistent value, there's nothing to return. But when you slice a sequence outside of bounds, you can still return an empty sequence.
Part of what's confusing here is that strings behave a little differently from lists. Look what happens when you do the same thing to a list:
>>> [0, 1, 2, 3, 4, 5][3]
3
>>> [0, 1, 2, 3, 4, 5][3:4]
[3]
Here the difference is obvious. In the case of strings, the results appear to be identical because in Python, there's no such thing as an individual character outside of a string. A single character is just a 1-character string.
(For the exact semantics of slicing outside the range of a sequence, see mgilson's answer.)
For the sake of adding an answer that points to a robust section in the documentation:
Given a slice expression like s[i:j:k],
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). When k is positive, i and j are reduced to len(s) if they are greater
if you write s[999:9999], python is returning s[len(s):len(s)] since len(s) < 999 and your step is positive (1 -- the default).
Slicing is not bounds-checked by the built-in types. And although both of your examples appear to have the same result, they work differently; try them with a list instead.

How can I code loop in python

I have a problem with the loop in python. I want create a list x[X0,X1,....Xn], with this algorithm:
X1=X0-(5+X0*2); X2=X1-(5+X1*2);.....
I try this but the result is not correct.
a=list(np.empty(10))
a[0]=1
for i in range(10):
a.append(a[i]-(5+a[i]*2))
print (a [i])
If you manually iterating the result gives:
[1,-6,1,-6, ....]
But after loop it gives:
[1,-1.29074375768e-231,2.19254982219e-314,.....]
The loop are easy in C but I did not understand the functioning in Python, if you have an idea ?
The problem is that list(np.empty(10)) doesn't do what you think it does. You expect it to return a list with 10 zeros, but it actually returns a list of 10 "random" numbers (actually, it returns a list of 10 uninitialized values).
From numpy docs:
empty, unlike zeros, does not set the array values to zero, and may
therefore be marginally faster. On the other hand, it requires the
user to manually set all the values in the array, and should be used
with caution.
(emphasize mine)
Instead, you should simply create a list with a single element, 1, and go on from there:
a = [1]
for i in range(10):
a.append(a[i] - (5 + a[i] * 2))
print(a)
# [1, -6, 1, -6, 1, -6, 1, -6, 1, -6, 1]
There's no reason to start your list with anything more than the single element. In C, you have to decide up front how many elements you want in your list, but that's not the case in Python, so doing list(np.empty(10)) creates a list with ten pointless elements in it that are just getting in your way.
a = [1] # This creates a list that starts off with the element 1.
for i in range(10):
a.append(a[i] - (5 + a[i] * 2))
print (a[i])
They way you have arranged your equation will constantly print out -6, and 1, therefore:
a = [1]
iti = 0
while(True):
thing = a[iti]-(5+(a[iti]*2))
a.append(thing)
iti+=1
print(thing)
I chose not to use the for loop to show a more understandable example, but you can still use that if you like.
I suggest you read up on functions, lists and for loops in python, as from your question it appears that you do not understand them very well.
There are several reasons why your code isn't currently working, but they are all easy fixed.
Firstly, you initiate a list with a numpy list, which is unnecessary.
a = list(np.empty(10))
can simply become
a = list()
or even simpler,
a = []
Next up, not an error but a suggestion,
a[0] = 1
can be removed and instead when you initialize the list, place one as the first item
a = [1]
after that, your code should work. So in summary:
n = 10 # or whatever number you want
a = [1]
for i in range(n - 1):
a.append(a[i] - (5 + a[i] * 2))
and if you want it as a function:
def formula_list(n):
a = [1]
for i in range(n - 1):
a.append(a[i] - (5 + a[i] * 2))
return a

Categories