简体   繁体   中英

Swapping List Elements During Iteration

I have read in several places that it is bad practice to modify an array/list during iteration. However many common algorithms appear to do this. For example Bubble Sort, Insertion Sort, and the example below for finding the minimum number of swaps needed to sort a list.

Is swapping list items during iteration an exception to the rule? If so why?

Is there a difference between what happens with enumerate and a simple for i in range(len(arr)) loop in this regard?

def minimumSwaps(arr):
    ref_arr = sorted(arr)
    index_dict = {v: i for i,v in enumerate(arr)}
    swaps = 0
    
    for i,v in enumerate(arr):
        print("i:", i, "v:", v)
        print("arr: ", arr)
        correct_value = ref_arr[i]
        if v != correct_value:
            to_swap_ix = index_dict[correct_value]
            print("swapping", arr[to_swap_ix], "with", arr[i])
            # Why can you modify list during iteration?
            arr[to_swap_ix],arr[i] = arr[i], arr[to_swap_ix]
            index_dict[v] = to_swap_ix
            index_dict[correct_value] = i
            swaps += 1
            
    return swaps
    

arr = list(map(int, "1 3 5 2 4 6 7".split(" ")))
assert minimumSwaps(arr) == 3

An array should not be modified while iterating through it, because iterators cannot handle the changes. But there are other ways to go through an array, without using iterators.

This is using iterators:

for index, item in enumerate(array):
   # don't modify array here

This is without iterators:

for index in range(len(array)):
   item = array[index]
   # feel free to modify array, but make sure index and len(array) are still OK

If the length & index need to be modified when modifying an array, do it even more "manually":

index = 0
while index < len(array):
   item = array[index]
   # feel free to modify array and modify index if needed
   index += 1

Modifying items in a list could sometimes produce unexpected result but it's perfectly fine to do if you are aware of the effects. It's not unpredictable.

You need to understand it's not a copy of the original list you ar iterating through. The next item is always the item on the next index in the list. So if you alter the item in an index before iterator reaches it the iterator will yield the new value.

That means if you for example intend to move all items one index up by setting item at index+1 to current value yielded from enumerate(). Then you will end up with a list completely filled with the item originally on index 0.

a = ['a','b','c','d']
for i, v in enumerate(a):
    next_i = (i + 1) % len(a)
    a[next_i] = v
print(a) # prints ['a', 'a', 'a', 'a']

And if you appending and inserting items to the list while iterating you may never reach the end.

In your example, and as you pointed out in a lot of algorithms for eg combinatoric and sorting, it's a part of the algorithm to change the forthcoming items.

An iterator over a range as in for i in range(len(arr)) won't adapt to changes in the original list because the range is created before starting and is immutable. So if the list has length 4 in the beginning, the loop will try iterate exactly 4 times regardless of changes of the lists length.

# This is probably a bad idea
for i in range(len(arr)):
    item = arr[i]
    if item == 0:
        arr.pop()

# This will work (don't ask for a use case)
for i, item in enumerate(arr):
    if item == 0:
        arr.pop()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM