Blargian
Blargian

Reputation: 350

Why doesn't using a for loop this way replace each item in the list?

I have a list which looks like this:

['G1X0.000Y3.000', 'G2X2.000Y3.000I1.000J2.291', 'G1X2.000Y-0.000', 'G2X0.000Y0.000I-1.000J-2.291']

The formatting is such that if the numeric content after X,Y,I or J are positive there is no + sign but if they are negative then the - sign is included. I am trying to loop through this list and to basically add the + sign if there is no - sign at the start of the numeric content. The result should look like this:

['G1X+0.000Y+3.000', 'G2X+2.000Y+3.000I+1.000J+2.291', 'G1X+2.000Y-0.000', 'G2X+0.000Y+0.000I-1.000J-2.291']

I'm trying to use a list comprehension to do so as follows:

#Make sure that X, Y, I and J start with + or - 
for count,i in enumerate(fileContents):
    if 'G' in i:
        indexOfI = i.index("X")
        if(i[indexOfI+1]!="-"):
            print(i[:indexOfI+1] + "+" + i[indexOfI+1:])
            fileContents[count] = i[:indexOfI+1] + "+" + i[indexOfI+1:]

        indexOfY = i.index("Y")
        if(i[indexOfY+1]!="-"):
            fileContents[count] = i[:indexOfY+1] + "+" + i[indexOfY+1:]

    if "G2" in i:
        indexOfI = i.index("I")
        if(i[indexOfI+1]!="-"):
            fileContents[count] = i[:indexOfI+1] + "+" + i[indexOfI+1:]

        indexOfJ = i.index("J")
        if(i[indexOfJ+1]!="-"):
            fileContents[count] = i[:indexOfJ+1] + "+" + i[indexOfJ+1:]

the statement print(i[:indexOfI+1] + "+" + i[indexOfI+1:]) gives an output in the console of:

G1X+0.000Y3.000
G2X+2.000Y3.000I1.000J2.291
G1X+2.000Y-0.000
G2X+0.000Y0.000I-1.000J-2.291

Which shows me that this performs what I want it to, however if I print fileContents after this function there are no changes to the list. In other words the following line of code does not replace the list item in each position as I expect it to:

fileContents[count] = i[:indexOfI+1] + "+" + i[indexOfI+1:]

Why does this not work when I can do the following and it does update the list correctly?

#Format each command to a 32 byte string
for i, s in enumerate(fileContents):
    fileContents[i] =s.ljust(32,'#')

edit: I originally titled the post "Why doesn't using a list comprehension this way replace each item in the list?". Users have kindly pointed out this has nothing to do with a list comprehension. I apologise, I thought this format x in list was a list comprehension.

Upvotes: 0

Views: 74

Answers (3)

trincot
trincot

Reputation: 350272

if I print fileContents after this function there are no changes to the list.

Actually, there are changes, but at most one + is added (the last one).

This is because you don't apply the same change to i, which means that the next if blocks will copy a part from i back to fileContents[count] that didn't have the previous change.

The quick fix is to make sure you apply the change also to i. Something like:

fileContents[count] = i = i[:indexOfI+1] + "+" + i[indexOfI+1:]
#                     ^^^^

You can perform this task with list comprehension using re.sub:

import re
fileContents = [re.sub(r"([XYIJ])(?=\d)", r"\1+", line) for line in fileContents]

This will match any X, Y, I or J followed by a digit. In that case, a plus is inserted between those. If you need more strict matching rules, where the line must start with "G", ...etc, then the regular expression will become more complex.

Upvotes: 2

buran
buran

Reputation: 14233

You can just add + after any of these chars, then replace back +- (f any) with -:

def my_replace(item):
    for char in 'XYIJ':
        item = item.replace(char, f'{char}+')
    return item.replace('+-', '-')

spam = ['G1X0.000Y3.000', 'G2X2.000Y3.000I1.000J2.291',
        'G1X2.000Y-0.000', 'G2X0.000Y0.000I-1.000J-2.291']
eggs = [my_replace(item) for item in spam] # now, this is list comprehension
print(eggs)

output

['G1X+0.000Y+3.000', 'G2X+2.000Y+3.000I+1.000J+2.291', 'G1X+2.000Y-0.000', 'G2X+0.000Y+0.000I-1.000J-2.291']

Upvotes: 1

MarianD
MarianD

Reputation: 14141

In the loop

for i, s in enumerate(fileContents):

you iterate over the fileContents list, which you want to change in the same loop. It's always dangerous.

Iterate over a copy of this list, which you may simply create by adding [:] to it:

for i, s in enumerate(fileContents[:]):

Upvotes: 1

Related Questions