This question already has answers here:
How do I reverse a list or loop over it backwards?
(37 answers)
Closed 2 years ago.
What are the differences between those four ways of reversing a list? Which one is faster?
1.
list.reverse()
2.
list(reversed(list))
3.
list[::-1]
4.
while(start < end):
list[start], list[end] = list[end], list[start]
start += 1
end -= 1`
I'll rename the list variable to xs to avoid confusion with the function list.
xs.reverse() mutates the original list, so that the values to the reference xs changes everywhere.
reversed(xs) creates an iterator, so calling list on it makes a new list with the values reversed. No mutations happen here.
xs[::-1] creates a new list reversed. This has the same effect as list(reversed(xs)). Not sure about which one is more performant though.
The procedure you wrote is also mutating, so that the original list xs changes.
If performance is not an issue, I would recommend non-mutating versions, as it can lead to surprising behaviour. In this case xs[::-1] is the simplest.
list.reverse()
Works on a reference. It means, that whenever you use it, you change the original list permanently.
list[::-1]
Is called reversing by slicing. It is not changing the state of the current list permanently. It is quite ineffective, since it creates a copy of the list that you are using it on, which takes up memory.
reversed()
Is not really that different. It works like slicing, but people often use it in for loop to create a reverse iterator, so a new list won't be created. If you print out a reversed list with reversed(), you can do it, but you will basically make a copy of a existing list, which is quite ineffective - but if you want to, you can do something like:
print(list(reversed(list_name)))
or if you want to print out elements of this reversed list
print(*reversed(list_name))
And I think the last solution works exactly like the first one, but it takes a lot of code. You have included only a part of it, but the full code would look something like that:
listt = [1,2,3,4,5,6,7]
start = 0
end = len(listt)-1
while(start < end):
listt[start], listt[end] = listt[end], listt[start]
start += 1
end -= 1
print(listt)
And that's a lot more code. Also, I am not pretty sure if it won't actually be slower.
It really depends on what you want in your program. If you simply want to show how the reversed list looks like, use slicing or reversed().
You can of course use them like the first method - reverse(), but you will have to assign those lists to a new variable, since (for example slicing) is not updating current list as mentioned above.
For example:
listt = [1,2,3,4,5,6,7]
a = listt[::-1]
print(listt)
#[1, 2, 3, 4, 5, 6, 7]
print(a)
#[7, 6, 5, 4, 3, 2, 1]
Related
This question already has answers here:
Why do these list operations (methods: clear / extend / reverse / append / sort / remove) return None, rather than the resulting list?
(6 answers)
Closed 3 years ago.
I made 2 lists (1 all strings, 1 all integers) using Python 3, tried to print and extend the first list with the other in the same statement, but ultimately got the output of none. Why?
# two lists, each with different data types
list_one = ["cat", "dog", "fish", "eye", "hook"]
list_two = [1, 2, 3, 4, 5]
# method that works and gives the expected output
list_one.extend(list_two)
print(list_one)
# method that doesn't work, gives an output of 'None'
print(list_one.extend(list_two))
Why does the second method give an output of 'None' instead of joining the two lists together?
Also, as I'm (very) new to programming, is it messy to put so many functions in a single statement?
print(list_one.extend(list_two))
This prints the return value of .extend(). extend has no return value/returns None. This is explicitly done so you're aware that extend modifies list_one in place, and doesn't return a new list. So you need to do this as a two-step process: modify the list, then print it.
You could do:
print(list_one + list_two)
This prints the "extended" list immediately, but doesn't modify either list and also doesn't store the extended list anywhere. To store the extended list you—again—need a two-step process:
list_three = list_one + list_two
print(list_three)
Get used to doing one thing at a time, instead of cramming everything into a single line. Readability is more important than line count. Python follows that doctrine and to some extend enforces it in ways like this.
This question already has answers here:
Understanding slicing
(38 answers)
Closed 4 years ago.
Okay, I'm sure this question is a bit trivial, but I'm curious!
In python, it is completely legal to do this:
a = [1,2,3,4,5]
print(a[:123])
>> [1, 2, 3, 4, 5]
but as soon as you try to do this:
a = [1,2,3,4,5]
print(a[123])
>> IndexError: list index out of range
Why is this so, in the context of how it is stored in memory and interpreted by the compiler?
I have tried storing the first example in a variable and checking the length of that variable but it is simply the length of the original list, ignoring the subsequent empty places.
I'm genuinely curious and interested to see what the answers are!
This defines your list with 5 elements
a = [1,2,3,4,5]
This tells you need to start from start to 124th index of the list
>> print(a[:123])
>> [1, 2, 3, 4, 5]
You are right in asking why this does not give a segmentation fault or some memory error as this statement should be illegal. There is no 123rd or 124th index.
Python here will start from the beginning and continue iterating until either:-
Maximum index (123) is reached OR
List items run out
In other words, If you ask C to walk 123 steps and there is an edge of cliff after 5 steps, C will jump off the cliff but Python will not
In case of
print(a[123])
You are not iterating from start but asking to jump directly to 123rd step. In this case both C and Python will crash with their own cry (IndexError or Segmentation Fault)
A simple answer will be that this is handled in the code.
While slicing if you are providing start or end values which are greater than the size of list, then it will be overwritten by last index of the list.
and when you look for exact index, it finds the index in the list, if not present it will show "Index out of range".
The syntax for slicing in Python 3.x follows as so,
a[start:end:step]
You can also use slicing in different ways, for example,
a[start:end]
a[start:]
a[:end]
a[:]
Setting the 'end' variable as something larger than the length of the list works because regardless of what you set 'end' to, slicing an array will always end at len(a) - 1
(Or at a[0] if you set 'step' to something negative, making the slice return the array reversed)
Answering why: List slicing creates a new list. It copies from the beginning to position 123, which doesn't exist, that's why it just slices to the known end. It seems the python devs decided to make the slicing robust, meaning it doesn't throw an error if you provide an index out of range, instead they just assume you want it to the actual end of the list. To absolutely concretely confirm that notion you'll have to ask the maintainer of the code ;)
This question already has answers here:
Removing Item From List - during iteration - what's wrong with this idiom? [duplicate]
(9 answers)
Closed 4 years ago.
I want to remove all even numbers in a list. But something confused me...
This is the code.
lst = [4,4,5,5]
for i in lst:
if i % 2 == 0:
print i
lst.remove(i)
print lst
It prints [4, 5, 5] Why not [5, 5]?
It should be like this
for i in lst[:]:
if i % 2 == 0:
print i
lst.remove(i)
print lst
Problem:
You are modifying the list while you iterate over it. Due to which the iteration is stopped before it could complete
Solution:
You could iterate over copy of the list
You could use list comprehension :
lst=[i for i in lst if i%2 != 0]
By using list.remove, you are modifying the list during the iteration. This breaks the iteration giving you unexpected results.
One solution is to create a new list using either filter or a list comprehension:
>>> filter(lambda i: i % 2 != 0, lst)
[5, 5]
>>> [i for i in lst if i % 2 != 0]
[5, 5]
You can assign either expression to lst if needed, but you can't avoid creating a new list object with these methods.
Other answers have already mentioned that you're modifying the list while iterating over it, and offered better ways to do it. Personally I prefer the list comprehension method:
odd_numbers = [item for item in numbers if item % 2 != 0]
For your specified case of a very small list, I would definitely go with that.
However, this does create a new list, which could be a problem if you have a very large list. In the case of integers, large probably means millions at least, but to be precise, it's however large it needs to be to start giving you issues with memory usage. In that case, here are a couple ways to do it.
One way is similar to the intent of the code in your question. You iterate over the list, removing the even numbers as you go. However, to avoid the problems that modifying a list you're iterating over can cause, you iterate over it backwards. There are ways to iterate forward, but this is simpler.
Here's one way using a while loop:
# A one hundred million item list that we don't want to copy
# even just the odd numbers from to put into a new list.
numbers = range(100000000) # list(range(100000000)) in Python 3
index = len(numbers) - 1 # Start on the index of the last item
while index >= 0:
if numbers[index] % 2 == 0:
numbers.pop(index)
index -= 1
Here's another way using a for loop:
# A one hundred million item list that we don't want to copy
# even just the odd numbers from to put into a new list.
numbers = range(100000000) # list(range(100000000)) in Python 3
for index in xrange(len(numbers) - 1, -1, -1): # range(...) in Python 3
if numbers[index] % 2 == 0:
numbers.pop(index)
Notice in both the while loop and for loop versions, I used numbers.pop(index), not numbers.remove(numbers[index]). First of all, .pop() is much more efficient because it provides the index, whereas .remove() would have to search the list for the first occurrence of the value. Second, notice that I said, "first occurrence of the value". That means that unless every item is unique, using .remove() would remove a different item than the one the loop is currently on, which would end up leaving the current item in the list.
There's one more solution I want to mention, for situations where you need to keep the original list, but don't want to use too much more memory to store a copy of the odd numbers. If you only want to iterate over the odd numbers once (or you're so averse to using memory that you'd rather recalculate things when you need to), you can use a generator. Doing so would let you iterate over the odd numbers in the list without needing any additional memory, apart from the inconsequential amount used by the generator mechanism.
A generator expression is defined exactly like a list comprehension, except that it's enclosed in parentheses instead of square brackets:
odd_numbers = (item for item in numbers if item % 2 != 0)
Remember that the generator expression is iterating over the original list, so changing the original list mid-iteration will give you the same problems as modifying a list while iterating over it in a for loop. In fact, the generator expression is itself using a for loop.
As an aside, generator expressions shouldn't be relegated only to very large lists; I use them whenever I don't need to calculate a whole list in one go.
Summary / TLDR:
The "best" way depends exactly what you're doing, but this should cover a lot of situations.
Assuming lists are either "small" or "large":
If your list is small, use the list comprehension (or even the generator expression if you can). If it's large, read on.
If you don't need the original list, use the while loop or for loop methods to remove the even numbers entirely (though using .pop(), not .remove()). If you do need the original list, read on.
If you're only iterating over the odd numbers once, use the generator expression. If you're iterating over them more than once, but you're willing to repeat computation to save memory, use the generator expression.
If you're iterating over the odd numbers too many times to recompute them each time, or you need random access, then use a list comprehension to make a new list with only the odd numbers in them. It's going to use a lot of memory, but them's the breaks.
As a general principle, you should not modify a collection while you are iterating over it. This leads to skipping of some elements, and index error in some cases.
Instead of removing elements from list, it would be easier if you just create another reference with same name. It has lesser time complexity too.
lst = filter(lambda i: i % 2 !=0, lst)
This problem is very simple to appreciate, here is the program -
hisc = [1,2,3,4]
print("\n", hisc)
ohisc = hisc
hisc.append(5)
print("\nPreviously...", ohisc)
print("\nAnd now...", hisc)
input("\nETE")
When I run it ohisc gets the 5. Why does ohisc change? How can I stop it from changing?
Apologies if this is something obvious.
Python variables are references. As such, the assignment copies the reference rather than the content of the variable.
In order to avoid this, all you have to do is create a new object:
ohisc = list(hisc)
This is using the list constructor which creates a new list from a given iterable.
Alternatively you can also assign from a slice (which creates a new object):
ohisc = hisc[:]
[] is the general slice operator which is used to extract a subset from a given collection. We simply leave out the start and end position (they default to the begin and end of the collection, respectively).
You definitely need to understand everything in Konrad Rudolph's answer. And I think in your specific case, that's what you want, too. But often there's a better way: If you avoid mutating objects (that is, changing them in-place), it never matters whether two names are referring to the same object or not. For example, you can change this:
hisc.append(5)
to this:
hisc = hisc + [5]
That doesn't change hisc in-place; it creates a new list, with the 5 added on to the end of it, and then assigns that to hisc. So, the fact that ohisc was pointing to the same list as hisc doesn't matter—that list is still there, unchanged, for ohisc to point to.
Let's say you wanted to replace all the negative values of the list with 0. That's pretty easy with mutation:
for i in range(len(lst)):
list[i] = max(list[i], 0)
But even easier without:
lst = [max(elem, 0) for elem in lst]
Now, what if you wanted to remove every negative list element? You can't change the shape of a sequence while looping over it, so you have to either make a copy of the list (so you can loop over one copy while you change the other), or come up with a more complicated algorithm (e.g., swap each 0 backward and then remove all the 0's at the end). But it's easy to do immutably:
lst = [elem for elem in lst if elem >= 0]
So, when would you ever want to mutate? Well, often you want two references to the same object, so when you update one, the other one sees the changes. In that case, you obviously have to have actual changes for the other one to see.
Here's a good explanation of what is happening: Python: copying a list the right way
Basically, you're making a pointer to the list but what you want to do is make a copy of the list.
Try this instead:
hisc = [1,2,3,4]
ohisc = hisc[:]
This question already has answers here:
Why do these list operations (methods: clear / extend / reverse / append / sort / remove) return None, rather than the resulting list?
(6 answers)
Closed 5 months ago.
I'm having an issue considering the built-in Python List-methods.
As I learned Python, I always thought Python mutators, as any value class mutators should do, returned the new variable it created.
Take this example:
a = range(5)
# will give [0, 1, 2, 3, 4]
b = a.remove(1)
# as I learned it, b should now be [0, 2, 3, 4]
# what actually happens:
# a = [0, 2, 3, 4]
# b = None
The main problem with this list mutator not returning a new list, is that you cannot to multiple mutations subsequently.
Say I want a list ranging from 0 to 5, without the 2 and the 3.
Mutators returning new variables should be able to do it like this:
a = range(5).remove(2).remove(3)
This sadly isn't possible, as range(5).remove(2) = None.
Now, is there a way to actually do multiple mutations on lists like I wanna do in my example? I think even PHP allows these types of subsequent mutations with Strings.
I also can't find a good reference on all the built-in Python functions. If anyone can find the actual definition (with return values) of all the list mutator methods, please let me know. All I can find is this page: http://docs.python.org/tutorial/datastructures.html
Rather than both mutating and returning objects, the Python library chooses to have just one way of using the result of a mutator. From import this:
There should be one-- and preferably only one --obvious way to do it.
Having said that, the more usual Python style for what you want to do is using list comprehensions or generator expressions:
[x for x in range(5) if x != 2 and x != 3]
You can also chain these together:
>>> [x for x in (x for x in range(5) if x != 2) if x != 3]
[0, 1, 4]
The above generator expression has the added advantage that it runs in O(n) time because Python only iterates over the range() once. For large generator expressions, and even for infinite generator expressions, this is advantageous.
Many methods of list and other mutable types intentionally return None so that there is no question in your mind as to whether you are creating a new object or mutating an existing object. The only thing that could be happening is mutation since, if a new object were created, it would have to be returned by the method, and it is not returned.
As you may have noticed, the methods of str that edit the string do return the new string, because strings are immutable and a new string is always returned.
There is of course nothing at all keeping you from writing a list subclass that has the desired behavior on .append() et al, although this seems like rather a heavy hammer to swing merely to allow you to chain method calls. Also, most Python programmers won't expect that behavior, making your code less clear.
In Python, essentially all methods that mutate the object return None.
That's so you don't accidentally think you've got a new object as a result.
You mention
I think even PHP allows these types of subsequent mutations with Strings.
While I don't remember about PHP, with string manipulations, you can chain method calls, because strings are immutable, so all string methods return a new string, since they don't (can't) change the one you call them on.
>>> "a{0}b".format(3).center(5).capitalize().join('ABC').swapcase()
'a A3B b A3B c'
Neither Python or php have built-in "mutators". You can simply write multiple lines, like
a = list(range(5))
a.remove(2)
a.remove(3)
If you want to generate a new list, copy the old one beforehand:
a = list(range(5))
b = a[:]
b.remove(2)
Note that in most cases, a singular call to remove indicates you should have used a set in the first place.
To remove multiple mutations on lists as you want to do on your example, you can do :
a = range(5)
a = [i for j, i in enumerate(a) if j not in [2, 3]]
a will be [0, 1, 4]