Eli
Eli

Reputation: 19

Regarding double loop in Python

I need help to fix this "index out of range" problem in this double for loop code I have.

aList = ["G", "D", "A", "G", "A", "B"]

for i in range(0, len(aList)-1):
    for j in range(i+1, len(aList)):
        if aList[i] == aList[j]:
            removeThis = aList[i]
            aList.remove(removeThis)

Basically I want to get the first non-repeating string in aList, which is supposed to be D in this case. Thank you in advance!

( edit ) : I saw many help given by using built-in Python functions, but I'm trying to do this code without built-in functions for learning purposes.

Upvotes: 1

Views: 71

Answers (4)

Thierry Lathuille
Thierry Lathuille

Reputation: 24232

A more efficient solution, in O(n): we iterate once on the list to get the counts (actually, we keep the list of indices, so we also have the first one).

Then, we keep the first of the ones who appeared only once.

A first solution, for latest versions of Python with ordered dicts (guaranteed since 3.7):

def first_unique(aList):
    seen = {}

    for index, value in enumerate(aList):
        seen.setdefault(value, []).append(index)

    for value, indices in seen.items():
        if len(indices)==1:  
            # as the dict is ordered, the first unique value we encounter
            # while iterating on the dict was the first one in the list
            return value

aList = ["G", "D", "A", "G", "A", "B"]         
print(first_unique(aList))
#'D'

For older versions of Python without ordered dict, we can filter the unique values, and keep the one with the smallest first index:

def first_unique_older_python(aList):
    seen = {}

    for index, value in enumerate(aList):
        seen.setdefault(value, []).append(index)

    uniques = ((indices[0], value) for value, indices in seen.items() if len(indices)==1)
    return min(uniques)[1]


aList = ["G", "D", "A", "G", "A", "B"]        


print(first_unique_older_python(aList))
#'D'

Upvotes: 2

Lior Pollak
Lior Pollak

Reputation: 3450

You can do this using reduce:

>>> aList = ["G", "D", "A", "G", "A", "B"]
>>> b = [alist[0]]
>>> reduce(lambda x,y: y not in b and b.append(y), aList)
>>> b
['D', 'A', 'G', 'B']
>>> b[0]
'D'

Note that the reduce here puts in b every non repeating element of aList in order, so the first element of b would be the first non-repeating string in aList.

Upvotes: -1

XPhyro
XPhyro

Reputation: 528

As @Arthur Tacca stated in their answer, you cannot remove an item from a list while iterating over it, as that will change the indices of the list and shorten the list you're getting an index error.

I would suggest a more concise, but SLOW way to do it, taking advantage of the built-in python functions:

aList = ["G", "D", "A", "G", "A", "B"]

counts = [aList.count(i) for i in aList]
first_non_repeating = aList[counts.index(min(counts))]

Upvotes: 0

Arthur Tacca
Arthur Tacca

Reputation: 9989

You can't remove an item from a list while you're iterating over it, it will break the iteration. If you only want the first non repeating string, as you say in your question, then you're OK because you can break out of the loop immediately which doesn't give a chance for the iterations to notice that the list has changed:

def remove_first_dupe(aList):
    for i in range(0, len(aList)-1):
        for j in range(i+1, len(aList)):
            if aList[i] == aList[j]:
                removeThis = aList[i]
                aList.remove(removeThis)
                return   # Added this

I put it into a function to make it easy to break out of both loops at once, using the return statement.

Upvotes: 1

Related Questions