3

I am dealing with some kind of problem and i could not find any solution. My problem is I am controlling a value in a nested list if it is not in another list and deleting it if it is not there but in the not in line it gives me an error like index out of range.

def heroes_updater(men_pref,women):
   for i in range(0,len(men_pref)-1):
      for j in range(0,len(men_pref[i])-1):
          if men_pref[i][j] not in women:
              men_pref[i]=men_pref[i][:j]+men_pref[i][j+1:]

example men_pref:

[['Storm', 'Black Widow', 'Scarlet Witch', 'Rouge', 'Mystique', 'Jean Grey', 'Ms. Marvel', 'Gamora', 'Invisible Woman', 'Elektra'], ['Storm', 'Elektra', 'Jean Grey', 'Scarlet Witch', 'Mystique', 'Ms. Marvel', 'Gamora', 'Rouge', 'Black Widow', 'Invisible Woman'], ['Invisible Woman', 'Scarlet Witch', 'Mystique', 'Black Widow', 'Ms. Marvel', 'Elektra', 'Jean Grey', 'Gamora', 'Storm', 'Rouge']]

example women:

['Jean Grey', 'Elektra', 'Mystique', 'Ms. Marvel', 'Rouge']

And the error is :

if men_pref[i][j] not in women:

IndexError: list index out of range

  • is it on purpose, that you skip the last or last two elements? – Daniel Dec 23 '15 at 18:29
  • what are you actually trying to achieve? – Padraic Cunningham Dec 23 '15 at 18:32
  • no, i was just trying something and as i know -1 must be there because len() function gives the info of how many elements are there and i am dealing with indexes which is 1 less than it. I've updated -2 as -1 –  Dec 23 '15 at 18:32
  • @PadraicCunningham for example Scarlet Witch is not on the women list and i wnat to delete it from men_pref[0] list but it is giving me an error for "not in" –  Dec 23 '15 at 18:33
  • @ulmotl Yes, `len` returns the number of elements being the last one at index `len(myList)-1`. But range doesn't return the last element, f.e. `range(1, 5)` returns `[1, 2, 3, 4]`. If you come from other languages like C/C++/Java... imagine the `for x in range(a,b,c)` as a `for (x = a; x – Mr. E Dec 23 '15 at 18:45
  • 1
    @Mr.E ops yeah you are right, my mistake. sorry. –  Dec 23 '15 at 18:48

3 Answers3

1

You're editing the list you're reading, you must never do that.

With the line men_pref[i]=men_pref[i][:j]+men_pref[i][j+1:]
you're removing an item from the list men_pref[i], but your j variable goes from 0 to the original lenght of the list, so you'll eventually have an index error when you check for men_pref[i][j] if j>len(men_pref[i])

EDIT: Of, if you want to edit your current list, then you'll have to read it with a backwards index (you start from the last, if it's not on the list of women, you remove it and then continue with the next item):

def heroes_updater(men_pref,women):
    for i in range(len(men_pref)-1, -1,-1):
        for j in range(len(men_pref[i])-1, -1, -1):
            if men_pref[i][j] not in women:
                men_pref[i].pop(j)
                # An alternative: del(mem_pref[i][j])

Another way would be to use list comprehension:

def heroes_updater(men_pref,women):
    for i in range(len(men_pref)-1, -1,-1):
        mem_pref[i] = [_w_ for _w_ in mem_pref[i] if _w_ in women]

There are other options, but I'll leave that to you. That's how you learn.

tglaria
  • 5,678
  • 2
  • 13
  • 17
  • Ok. I ve understand it. It gives an error because its length and indexes of course is changing. –  Dec 23 '15 at 18:38
  • "You must never do that" -- you can easily do it if you understand what's happening. But in this case, you're quite right, it only adds to the confusion. – kindall Dec 23 '15 at 18:38
  • Thanks for solutions since I know the what cause an error I can manage it Thank you. –  Dec 23 '15 at 18:45
  • You can, you shouldn't if you're checking to whole list. I've yet to see a case where it's the best way to write code. As far as I've seen is just a reason for bugs, specially when you change something some time later and forget about it. – tglaria Dec 23 '15 at 18:46
1

By removing elements from your list, the list gets shorter j is larger than the length of the list. To circumvent this problem, just don't alter the lists, but create a new one:

def heroes_updater(men_pref,women):
   result = []
   for prefs in men_pref:
      new_prefs = []
      for pref in prefs:
          if pref in women:
              new_prefs.append(pref)
      result.append(new_prefs)
   men_pref[:] = result

or better:

def filter_non_heroes(men_pref,women):
    return [
        [pref for pref in prefs if pref in women]
        for prefs in men_pref
    ]
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • Thank you. It works very well and since I understand the problem, your solution makes great sense. –  Dec 23 '15 at 18:40
1

You can use a set with a list comp, you cannot iterate over and mutate a list as each time you remove an element the list gets smaller and your index is based on what the size of the list when you started the range:

men = [['Storm', 'Black Widow', 'Scarlet Witch', 'Rouge', 'Mystique', 'Jean Grey', 'Ms. Marvel', 'Gamora', 'Invisible Woman', 'Elektra'], ['Storm', 'Elektra', 'Jean Grey', 'Scarlet Witch', 'Mystique', 'Ms. Marvel', 'Gamora', 'Rouge', 'Black Widow', 'Invisible Woman'], ['Invisible Woman', 'Scarlet Witch', 'Mystique', 'Black Widow', 'Ms. Marvel', 'Elektra', 'Jean Grey', 'Gamora', 'Storm', 'Rouge']]

wom = {'Jean Grey', 'Elektra', 'Mystique', 'Ms. Marvel', 'Rouge'}

men[:] = [[ele for ele in sub if ele in wom] for sub in men]

print(men)

Or functionally if order is irrelevant:

men = [['Storm', 'Black Widow', 'Scarlet Witch', 'Rouge', 'Mystique', 'Jean Grey', 'Ms. Marvel', 'Gamora', 'Invisible Woman', 'Elektra'], ['Storm', 'Elektra', 'Jean Grey', 'Scarlet Witch', 'Mystique', 'Ms. Marvel', 'Gamora', 'Rouge', 'Black Widow', 'Invisible Woman'], ['Invisible Woman', 'Scarlet Witch', 'Mystique', 'Black Widow', 'Ms. Marvel', 'Elektra', 'Jean Grey', 'Gamora', 'Storm', 'Rouge']]

wom = {'Jean Grey', 'Elektra', 'Mystique', 'Ms. Marvel', 'Rouge'}

men[:] = map(list,map(wom.intersection, men))

print(men)

You could also start from the end of the list, using your range logic but using range(len(sub)-1,-1, -1) but it is easier to just to use reversed and iterate over the elements themselves:

def heroes_updater(men_pref, women):
    for sub in men_pref:
        for m in reversed(sub):
            if m not in women:
                sub.remove(m)
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321