2013-10-11 5 views
1

Я новичок в Python. Раньше я изучал другие языки, такие как C++ (начинающий) и JQuery. Но я считаю, что цикл в python довольно запутан.Неожиданный индексError при удалении элементов списка

Ну, я хочу достичь простого результата. Программа будет цикл по списку слов, а затем он будет удалять слова, которые соответствуют с первых двух букв со следующим словом в списке:

test = ['aac', 'aad', 'aac', 'asd', 'msc'] 
for i in range(len(test)): 
    if test[i][0:2] == test[i+1][0:2]: 
     test.remove(test[i]) 

# This should output only ['aac', 'asd', 'msc'] 
print test 

выше код должен удалить 'aac' и 'aad' из список. Но на самом деле это поднимает IndexError. Кроме того, я не смог достичь желаемого результата. Не могли бы вы объяснить?

ответ

2

You изменяют длину списка при циклировании по диапазону, который увеличивается до начальной длины списка; удалить один элемент из списка, а последний индекс больше недействителен.

Перемещение, поскольку элементы удаляются из списка по текущему индексу, остальные индексы списка shift; то, что было в индексе i + 1, теперь находится в индексе i, и ваш индекс цикла больше не является полезным.

И последнее, но не менее важное: вы зацикливаете до последнего индекса test, но затем попытайтесь получить доступ к test[i + 1]; этот индекс не существует, даже если вы не удаляли элементы из списка.

Вы можете использовать while цикл, чтобы добиться того, что вы хотите сделать:

test = ['aac', 'aad', 'aac', 'asd', 'msc'] 
i = 0 
while i < len(test) - 1: 
    if test[i][:2] == test[i+1][:2]: 
     del test[i] 
     continue 
    i += 1 

Теперь i проверяется на длине новый каждый цикл итерации, и мы только увеличиваем i, если ни один элемент не был удален. Обратите внимание, что цикл ограничен длиной минус 1, потому что вы хотите протестировать для test[i + 1] каждую итерацию.

Обратите внимание, что я использую del test[i]; нет необходимости сканировать в списке, ища это значение для удаления снова; это может привести к тонким ошибкам, если значения появляются несколько раз в списке, но только позже экземпляры должны быть удалены; например ['aac', 'foo', 'aac', 'aad'] должно привести к ['aac', 'foo', 'aad'], не['foo', 'aac', 'aad'], что и test.remove(test[i]) приведет к

Demo:.

>>> test = ['aac', 'aad', 'aac', 'asd', 'msc'] 
>>> i = 0 
>>> while i < len(test) - 1: 
...  if test[i][:2] == test[i+1][:2]: 
...   del test[i] 
...   continue 
...  i += 1 
... 
>>> test 
['aac', 'asd', 'msc'] 

Вы можете использовать список понимание, чтобы избежать усадки проблемы списка:

>>> [t for i, t in enumerate(test) if i == len(test) - 1 or t[:2] != test[i + 1][:2]] 
['aac', 'asd', 'msc'] 

Оба подхода требуют только одного цикла через список ввода.

+0

Теперь я понимаю. Сначала я подумал, что len (range (test)) будет обновляться по мере удаления элементов списка. Но потом я понял, что моя мысль была глупой! Я бы использовал метод while, потому что он выглядит лучше всего для меня. Благодарю. Один вопрос: вы использовали метод «продолжить», но это действительно необходимо? –

+0

Если вы не используете 'continue', вам придется использовать' else: '; вы не хотите, чтобы 'i + = 1' запускался, когда вы только что удалили' test [i] '. –

+0

Метод, предложенный @Manoj, должен был работать частично. Этот метод способен обрабатывать ошибку «i + 1 not existing». Но результат совершенно неожиданен. Этот код удаляет только первый элемент списка и выводит ['aad', 'aac'. 'asd', 'msc'] –

2

Как вы удаляете элементы из списка, range(len(test)) все еще имеет такое же значение. Поэтому, даже если ваш список test имеет только никаких элементов, цикл все еще продолжается.

У меня есть два решения:

  1. Скопируйте элементы, которые вы хотите новый список, так что вместо его удаления:

    test2 = test[i] 
    

    И не забудьте поменять местами условия.

  2. Проложить его назад. Как это:

    n = len(test) 
    for i in range(n): 
        j = n - i - 1 
        if j > 1: 
        if test[j][0:2] == test[j-1][0:2]: 
         test.remove(test[j]) 
    

    Или, как это было предложен Мартейн:

    n = len(test) 
    for i in range(n-1, 0, -1): 
        if i > 1: 
        if test[i][0:2] == test[i-1][0:2]: 
         test.remove(test[i]) 
    

Надеется, что это помогает!

P.S простите за мой глупый, предыдущий ответ

+0

Ну, он не технически итерации по списку при удалении элементов из него. Он выполняет итерацию над 'range (len (test))' и удаляет элементы из 'test', а не итерации над' test' при удалении из него. Проблема в том, что ему нужно вытащить элемент из 'range (len (test))' каждый раз, когда он убивает что-то в 'test' – inspectorG4dget

+0

Кроме того, вы по-прежнему удаляете из' test', что приведет к той же ошибке все снова – inspectorG4dget

+0

'test' и' test2' начинаются с равного размера. Но поскольку вы удаляете вещи в 'test2', его размер уменьшается. Это означает, что 'test [i]' и 'test2 [i]' больше не будут ссылаться на один и тот же объект. Следовательно, вы все равно можете столкнуться с ошибкой индекса.Далее 'test2 = test' заставляет обе переменные ссылаться на один и тот же список, а не на две отдельные копии' test'. Поэтому 'test2.remove (...)' эквивалентно 'test.remove (...)' в этом случае. Я настоятельно рекомендую проверить ваш код, прежде чем отправлять его – inspectorG4dget

0

for i in range(len(test)) дает вам список с действительными показателями test. Однако, поскольку вы сохраняете удаление элементов из test в цикле, размер test уменьшается, в результате чего некоторые из этих первоначально действующих индексов становятся недействительными.

Что вы делаете что-то вроде этого:

L = range(len(test)) 
for i in L: 
    if condition: 
    # remove something from test <- the size of test has changed. 
           # L[-1] is no longer a valid index in test 

Что вы можете сделать вместо этого, чтобы аккумулировать показатели вещей, которые вы хотели бы удалить, и удалить их позже:

deleteThese = set() 
for i,item in enumerate(test[:-1]): 
    if item[0:2] == test[i+1][0:2]: 
    deleteThese.add(i) 
test = [item for i,item in enumerate(test) if i not in deleteThese] 

Выход:

In [70]: test = ['aac', 'aad', 'aac', 'asd', 'msc'] 

In [71]: %paste 
deleteThese = set() 
for i,item in enumerate(test[:-1]): 
    if item[0:2] == test[i+1][0:2]: 
    deleteThese.add(i) 
test = [item for i,item in enumerate(test) if i not in deleteThese] 

## -- End pasted text -- 

In [72]: test 
Out[72]: ['aac', 'asd', 'msc'] 
+0

Вы могли бы избежать двойного цикла, вместо этого создав список элементов, чтобы * keep *. –

1

Как и другие, когда вы удаляете элементы, список становится короче, вызывая ошибку индекса.

Сохранение в соответствии с оригинальным вопросом. если вы ищете, чтобы удалить элементы с помощью list.remove() вы можете добавить найденные элементы в списке, то перебрать их и удалить их из исходного списка, так как:

# Set up the variables 
test = ['aac', 'aad', 'aac', 'asd', 'msc'] 
found = [] 
# Loop Over the range of the lenght of the set 
for i in range(len(test)): 
    try: 
     if test[i].startswith(test[i+1][0:2]): 
      found.append(test[i]) # Add the found item to the found list 
    except IndexError: # You'll hit this when you do test[i+1] 
     pass 

# Remove the Items at this point so you don't cause any issues 
for item in found: 
    test.remove(item) # If an item has been found remove the first instance 

# This sholuld output only ['aac', 'asd', 'msc'] 
print test 

EDIT:

В соответствии с Martins комментарий, вам не нужно сделать второй список элементов, которые должны быть удалены вы можете вместо того, чтобы сделать список элементов, которые не должны быть удалены, как так:

# Set up the variables 
test = ['aac', 'aad', 'aac', 'asd', 'msc'] 
found = [] 

# Loop Over the range of the lenght of the set 
for i in range(len(test)): 
    try: 
     if not test[i].startswith(test[i+1][0:2]): 
      found.append(test[i]) # Add the found item to the found list 
    except IndexError: # You'll hit this when you do test[i+1] 
     found.append(test[i]) # If there is no test[i+1], test[i] must be cool. 


# This sholuld output only ['aac', 'asd', 'msc'] 
print found 
+0

Почему бы не построить 'found' из элементов, которые делают * not *, необходимо удалить? Тогда у вас уже есть свой новый список! –

+0

Отличная идея martin обновит мой ответ, спасибо, что нашли время для комментариев! – Noelkd

+0

Обновил мой ответ, если бы вы могли взглянуть на него Martijn было бы полезно – Noelkd

Смежные вопросы