[Tutor] IndexError: list index out of range [ Program work fine , but gives this message , guidance requested ]

Brian van den Broek broek at cc.umanitoba.ca
Sun Jan 8 22:03:52 CET 2006


John Joseph said unto the world upon 08/01/06 06:36 AM:
> --- Guillermo Fernandez Castellanos
> <guillermo.fernandez.castellanos at gmail.com> wrote:
> 
> 
>>Hi,
>>
>>Look at this:
>> >>> i=[1,2,3]
>> >>> i[len(i)]
>>Traceback (most recent call last):
>>   File "<stdin>", line 1, in ?
>>IndexError: list index out of range
>>
>>
>>This means that I have tried to access an element
>>that is out of the 
>>list, in this case i[3], that is, the 4th element of
>>the list.
>>
>>You are doing the same. You do a
>>j in range(len(seats))
>>and then you are accessing your list with seats[j]
>>and seats[j+1]. What 
>>happens when j = len(seats)-1 and you call
>>seats[j+1]? :-)
>>
>>It happens indeed the same that in the example I
>>gave you first.
>>
>>Hope it helps. Good luck,
>>
> 
> 
> Thanks for the advice  
>  But I need to display the results 
>   What should I do in the for loop  for this message
> to go 
>   my for loop is as 
> 
> for   j in range(length)  :
>         if seats[j] <> seats[j+1]:
>                 print " The Seat", j , "And the seat",
> j+1 , "value, which are", seats[j]," and", seats[j+1],
> "are not same" 
>         else:
>                 print "The Seats ",j ,"And the seats",
> j+1, "Values,", seats[j], "and", seats[j+1], "are
> same"
>       Thanks 
>          Joseph John 


Joseph,

I think Guillermo has already given you the answer :-).  But, I'll 
make it more explicit.

I'll also do a rather more. My intent is to give you some idea of how 
you can go about incrementally improving your code.

The code you posted is doing something like:

 >>> a_list = [7,8,7]
 >>> for index in range(len(a_list)):
	print index, a_list[index]
	print index + 1, len(a_list), a_list[index + 1]

	
0 7
1 3 8
1 8
2 3 7
2 7
3 3

Traceback (most recent call last):
   File "<pyshell#97>", line 3, in -toplevel-
     print index + 1, len(a_list), a_list[index + 1]
IndexError: list index out of range

Examine the output. When the iteration hits the last member of the 
list (when it gets to the final element of range(len(a_list)), there 
is no next element of the list, so the request to print the next 
element doesn't work. The list length is 3, so I get the same 
exception as if I'd done:

 >>> a_list[3]

Traceback (most recent call last):
   File "<pyshell#16>", line 1, in -toplevel-
     a_list[3]
IndexError: list index out of range

directly. That suggests *not* asking for an iteration which uses up 
the list -- leave a next element and the code will work:

 >>> for index in range(len(a_list) - 1):  # Note the difference
	print index, a_list[index]
	print index + 1, len(a_list), a_list[index + 1]

	
0 7
1 3 8
1 8
2 3 7
 >>>


Of course, as you pointed out in your OP, right now, even with this 
fix, you will only be testing for sequential duplicates. My test list

a_list = [7,8,7]

has dupes, but your approach won't find them.

Warning: I'm going to go into a number of issues that you might not 
yet entirely understand. If I lose you, don't worry; just ask about it.


Let's both fix the requirement that the duplicates be sequential and 
start putting the code into functions.

Consider this:

 >>> def dupe_detector_1(sequence):
	for item in sequence:
		if sequence.count(item) > 1:
			print "%s is duplicated!" %item

			
 >>> dupe_detector_1(a_list)
7 is duplicated!
7 is duplicated!
 >>>

OK, that might be better, as we can find non-sequential duplicates. 
But, there are two problems. 1) The duplication warning is duplicated 
:-) and 2)

 >>> a_tuple = (4,3,4)
 >>> dupe_detector_1(a_tuple)

Traceback (most recent call last):
   File "<pyshell#47>", line 1, in -toplevel-
     dupe_detector_1(a_tuple)
   File "<pyshell#28>", line 3, in dupe_detector_1
     if sequence.count(item) > 1:
AttributeError: 'tuple' object has no attribute 'count'
 >>>

The function signature seems to indicate it will work for all 
sequences, but it chokes on tuples.

The second problem is easy to fix:

 >>> def dupe_detector_2(sequence):
	# coerce to a list as lists have count methods.
	sequence = list(sequence)
	for item in sequence:
		if sequence.count(item) > 1:
			print "%s is duplicated!" %item

			
 >>> dupe_detector_2((1,1,2))
1 is duplicated!
1 is duplicated!
 >>>

1 down, 1 to go.

 >>> def dupe_detector_3(sequence):
	sequence = list(sequence)
	seen_dupes = []
	for item in sequence:
		if item in seen_dupes:
			# if it is there, we already know it is duplicated
			continue
		elif sequence.count(item) > 1:
			print "%s is duplicated!" %item
			seen_dupes.append(item)

			
 >>> dupe_detector_3(a_list)
7 is duplicated!
 >>>

That's much better :-)


There remain 2 things I'd want to do differently.

First, I'd separate the print logic from the detection logic:

 >>> def dupe_detector_4(sequence):
	'''returns a list of items in sequence that are duplicated'''
	sequence = list(sequence)
	seen_dupes = []
	for item in sequence:
		if item in seen_dupes:
			# if it is there, we already know it is duplicated
			continue
		elif sequence.count(item) > 1:
			seen_dupes.append(item)
	return seen_dupes

 >>> big_list = [1,42,3,4,2,3,54,6,7,3,45,6,4,32,43,5,32,4,4,42,3,42,3]
 >>> dupes_in_big_list = dupe_detector_4(big_list)
 >>> dupes_in_big_list
[42, 3, 4, 6, 32]

OK, you might say "fine, but I *wanted* an on-screen report". Let's do 
that this way:

 >>> def print_dupe_report(sequence):
	'''prints a report of items in sequence that are duplicated'''
	dupes = dupe_detector_4(sequence)
	dupes.sort()
	for d in dupes:
		print "%s was duplicated" %d

		
 >>> print_dupe_report(big_list)
3 was duplicated
4 was duplicated
6 was duplicated
32 was duplicated
42 was duplicated
 >>>

The advantage of this is you might well want to do what 
dupe_detector_4 does without the screen output. Making the function 
have a smaller job makes it easier to reuse in other contexts.

The last thing is a bit subtle. Say I had a sequence of length 10**6 
where most items were duplicated. dupe_detector_4 has to iterate over 
the entire long sequence, and continually hit the continue clause as 
it will have seen the item already.

This last version will fix that, too:

 >>> def dupe_detector_5(sequence):
	'''returns a list of items in sequence that are duplicated'''
	sequence = list(sequence)
	seen_dupes = []
	for item in set(sequence):   # Note the difference
		if sequence.count(item) > 1:
			seen_dupes.append(item)
	return seen_dupes

 >>> dupes_in_big_list = dupe_detector_5(big_list)
 >>> dupes_in_big_list
[32, 3, 4, 6, 42]
 >>>

We no longer need the continue clause, as converting to set ensures we 
won't ever deal with the same item twice:

 >>> set((1,1,1,2,2,2,2,2,3,4,4,5))
set([1, 2, 3, 4, 5])


And, preventing us from dealing with the same item twice is what makes 
this better. To see that, consider:

 >>> def iteration_comparison(sequence):
	list_count = 0
	set_count = 0
	for i in list(sequence):
		list_count += 1
	for i in set(sequence):
		set_count += 1
	print list_count, set_count

	
 >>> iteration_comparison((1,2,3,4))
4 4
 >>> iteration_comparison((1,2,3,4,1,2,3,4,1,2,3,4,5,1,2,3,4,1,2,3,4))
21 5
 >>>

For sequences with a lot of duplication, the set version (as in 
dupe_detector_5) has to iterate over far fewer items. That'll be quicker.

OK, I hope most of that made sense.

Best,

Brian vdB




More information about the Tutor mailing list