2016-02-29 5 views
0

Я пытаюсь удалить классы из списка на основе их hp. Я делаю крупномасштабный боевой симулятор для кампании D & D. Его простая программа, которая делает два списка классов и ямы их друг против друга.Удаление класса из списка

Я столкнулся с проблемой, когда дело доходит до удаления мертвых истребителей. Он отлично работает, если один истребитель умирает в раунде, но когда несколько умирают, он становится неуклюжим.

def check_human_deaths(): 
    for i in range(len(goodguys)): 
     if goodguys[i].hp <= 0: 
      print('{} has died...'.format(goodguys[i].name)) 
      goodguys.remove(goodguys[i]) 

Удаление мертвого бойца изменяет длину списка, метание ошибки индекса:

IndexError: list index out of range 

Я не уверен, как поступить с удалением мертвых с поля боя. Любые советы приветствуются. Дайте мне знать, если я буду об этом по-настоящему неправильно.

+5

Не изменяйте списки при их итерации по ним. – Jasper

+7

http://stackoverflow.com/questions/1207406/remove-items-from-a-list-while-iterating-in-python –

ответ

0

Вы получаете эту ошибку, потому что выполняете итерацию по длине списка, скажем l. Каждый раз, когда вы вызываете list.remove(), длина списка уменьшается на 1, и ваша программа дает индексную ошибку, потому что list[l] не существует. Вместо этого вы можете использовать:

def check_human_deaths(): 
    for goodguy in list(goodguys): 
     if goodguy.hp <= 0: 
      print('{} has died...'.format(goodguy.name)) 
      goodguys.remove(goodguy) 

Примечание: list(goodguys) создаст копию goodguys списка препятствующего странное поведение for цикла, когда объект удаляется из списка:

>>> t= [1,2,3] 
>>> x= list(t) 
>>> x.remove(1) 
>>> x 
[2, 3] 
>>> t 
[1, 2, 3] 

Даже после удаления значения из x, это значение будет по-прежнему присутствовать в t, а ваш для цикла for не будет вести себя странно

+0

Вы правы. Я не проверял, выполняет ли 'list()' глубокую копию объектов или просто копирует ссылки и возможную проблему 'global'' goodguys', но мой последний пункт был неправильным. –

+1

Как раз для того, чтобы исправить вас, 'list()' не выполняет 'deep copy', а выполняет' мелкая копия'. Объекты класса в списке все равно останутся прежними. 'list()' эквивалентен 'copy.copy()' где для глубокой копии в python у нас есть 'copy.deepcopy()' (что даже создаст копию объектов класса в списке). –

+0

Меньше исправления и больше разъяснений. Спасибо :) И солидный ответ на общий вопрос. –

1

Проблема в том, что при удалении из goodguys индекс уменьшается на единицу. Пример:

1,2,3,4 

Удалить 2

1,3,4 

Индекс трех была уменьшена на один и размер был уменьшен на один.

3

Два варианта:

Изменять копию списка и использовать результат того, что в качестве нового списка:

>>> lst = [1,2,3,4,5] 
>>> for i in lst[:]: 
...  if i % 2: 
...   result.append(i) 
... 
>>> lst = result 
>>> lst 
[1, 3, 5] 

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

>>> lst = [1,2,3,4,5] 
>>> for i in lst[::-1]: 
...  if not i % 2: 
...   lst.remove(i) 
... 
>>> lst 
[1, 3, 5] 
+0

'lst [:] = [...]' будет лучшей альтернативой –

+0

Извините, я сейчас плотный - как это? – bgporter

+0

Более эффективный, хотя OP может захотеть печатать по мере их поступления, в этом случае 'for i в обратном (lst)' будет, по крайней мере, избегать копирования. –

0

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

def check_human_deaths(): 
    things_to_delete = [] 
    for i in range(len(goodguys)): 
     if goodguys[i].hp <= 0: 
      print('{} has died...'.format(goodguys[i].name)) 
      things_to_delete.append(goodguys[i]) 
    goodguys = [x for x in goodguys if x not in things_to_delete] 
1
goodguys = [ guy for guy in goodguys if guy.hp > 0 ] 

Это будет отфильтровывать все мертвые Goodguys в массиве.

Вы можете использовать его в своей функции, как это:

def check_human_deaths(): 
    global goodguys 
    dedguys = [ guy for guy in goodguys if guy.hp <= 0 ] 
    for guy in dedguys: 
     print('{} has died...'.format(guy.name)) 
    goodguys = [ guy for guy in goodguys if guy.hp > 0 ] 
0

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

class Foo(): 
    def __init__(self, hp, name): 
     self.hp = hp 
     self.name = name 


def check_human_deaths(goodguys): 
    for guy in goodguys: 
     if guy.hp < 1: 
      print('{} has died...'.format(guy.name)) 
     else: 
      yield guy 

Демо:

In [29]: goodguys = [Foo(0,"foo"), Foo(1,"bar"), Foo(2,"foobar"), Foo(-1,"barf")] 

In [30]: goodguys[:] = check_human_deaths(goodguys) 
foo has died... 
barf has died... 

In [31]: 

In [31]: print(goodguys) 
[<__main__.Foo instance at 0x7fdab2c74dd0>, <__main__.Foo instance at 0x7fdab2c6e908>] 

У этого answer есть очень хорошее объяснение, почему вы никогда не должны мутировать список, который вы итерируете.

Кроме того, если вы собираетесь начать в конце списка и удалить пользователя, вы можете использовать обратный вместо того, чтобы создать другую копию списка:

for guy in reversed(goodguys): 
     if guy.hp <= 0: 
      print('{} has died...'.format(guy.name)) 
      goodguys.remove(guy) 

Но это не будет так же эффективны, как и первый вариант.

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