开发者

difference between 2 pieces Python code

I'm doing an exercise as following:

# B. front_x
# Given a list of strings, return a list with the strings
# in sorted order, except group all the strings that begin with 'x' first.
# e.g. ['mix', 'xyz', 'apple', 'xanadu', 'aardvark'] yields
# ['xanadu', 'xyz', 'aardvark', 'apple', 'mix']
# Hint: this can be done by making 2 lists and sorting each of them
# before combining them.

sample solution:

def front_x(words):
  listX = []
  listO = []

  for w in words:
    if w.startswith('x'):
      listX.append(w)
    else:
      listO.append(w)

  listX.sort()
  listO.sort()

  return listX + listO

my solution:

def front_x(words):
  listX = []

  for w in words:
    if w.startswith('x'):
      listX.append(w)
      words.remove(w)

  listX开发者_StackOverflow中文版.sort()
  words.sort()

  return listX + words  

as I tested my solution, the result is a little weird. Here is the source code with my solution: http://dl.dropbox.com/u/559353/list1.py. You might want to try it out.


The problem is that you loop over the list and remove elements from it (modifying it):

for w in words:
    if w.startswith('x'):
      listX.append(w)
      words.remove(w)

Example:

>>> a = range(5)
>>> for i in a:
...  a.remove(i)
... 
>>> a
[1, 3]

This code works as follows:

  • Get first element, remove it.
  • Move to the next element. But it is not 1 anymore because we removed 0 previously and thus 1 become the new first element. The next element is therefore 2 and 1 is skipped.
  • Same for 3 and 4.


Two main differences:

  1. Removing an element from a list inside loop where the list is being iterated doesn't quite work in Python. If you were using Java you would get an exception saying that you are modifying a collection that is being iterated. Python doesn't shout this error apparently. @Felix_Kling explains it quite well in his answer.
  2. Also you are modifying the input parameter words. So the caller of your function front_x will see words modified after the execution of the function. This behaviour, unless is explicitly expected, is better to be avoided. Imagine that your program is doing something else with words. Keeping two lists as in the sample solution is a better approach.


Altering the list you're iterating over results in undefined behaviour. That's why the sample solution creates two new lists instead of deleting from the source list.

for w in words:
  if w.startswith('x'):
    listX.append(w)
    words.remove(w) # Problem here!

See this question for a discussion on this matter. It basically boils down to list iterators iterating through the indexes of the list without going back and checking for modifications (which would be expensive!).

If you want to avoid creating a second list, you will have to perform two iterations. One to iterate over words to create listX and another to iterate over listX deleting from words.


That hint is misleading and unnecessary, you can do this without sorting and combining two lists independently:

>>> items = ['mix', 'xyz', 'apple', 'xanadu', 'aardvark']
>>> sorted(items, key=lambda item: (item[0]!='x', item))
['xanadu', 'xyz', 'aardvark', 'apple', 'mix']

The built-in sorted() function takes an option key argument that tells it what to sort by. In this case, you want to create a tuples like (False, 'xanadu') or (True, 'apple') for each element of the original list, which you can do with a lambda.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜