不要用for迴圈一面走訪一面移除list的元素

更新於 發佈於 閱讀時間約 6 分鐘

在寫《The Nature of Code閱讀心得筆記——使用Python實作》《The Nature of Code閱讀心得與Python實作》第4.3節時,原書提到,在使用Java的ArrayList時,如果用迴圈一面走訪一面又移除其中的元素,那會有難以察覺的問題存在。寫個小程式測試的結果發現,Python的list也會有一樣的問題。如果不是原書有特別提到,還真的不會察覺這個問題,因為程式跑起來完全沒有異狀,看不出來有任何不對勁的地方。

到底是個什麼樣奇特的問題,能讓人渾然不覺呢?看一下要寫的東西就知道為什麼了。

那一章是在談粒子系統(particle system)的模擬,其中一個效果就是粒子會在畫面上不斷冒出來,過了一段時間之後又會自動消失,所以粒子的數量會一直變動。這個用list來寫剛剛好,因為list有append()和remove()這兩個方法,可以新增、移除裡頭的元素。把那些不斷冒出來又消失的粒子放在list裡頭,每次更新畫面時,利用append()把新的粒子加到list裡頭,並利用for迴圈來走訪list裡頭所有的粒子,當發現該被移除的粒子時,就把那個粒子用remove()移除。

挺容易的,不是嗎?不過壞就壞在那個移除粒子的部分,如果沒注意到一個小細節,那每次更新畫面用for迴圈走訪list中的所有元素時,可能會有幾個漏掉沒走訪到,因而沒有更新那幾個粒子的狀態。因為畫面是一直在不斷更新的,這次漏掉沒更新狀態的粒子,下次可能就不會被漏掉,再加上畫面中一大堆的粒子,即便有幾個粒子的狀態沒有更新,還真的是很難察覺。那為什麼會這樣呢?用個簡單的例子來看,就會很清楚了。

下面這段程式,最後會印出什麼呢?是[4, 5]嗎?

a = [1, 2, 3, 4, 5]
for i in a:
if i<=3:
a.remove(i)

print(a)

這段程式的目的,是把1、2、3三個元素從a裡頭移除。不過,最後印出來的答案卻是[2, 4, 5],也就是說,漏掉2沒移除。為什麼?!到底發生了什麼事?

一開始的時候,也就是當我們位於a[0]時,因為a[0]的值是1,所以我們把1這個元素從a中移除。這時候,a會變成[2, 3, 4, 5],而我們還是在a[0]這個位置。

既然a[0]已經處理過了,不管實際上a變成什麼樣子,反正接下來當然就是要處理a[1],也就是3。因為3<=3,所以把3也移除,a變成[2, 4, 5]。

a[1]處理過了,再來就是處理a[2],也就是5。因為5>3,所以沒有被移除,最後剩下的,就是[2, 4, 5]。

稍微精簡一下這過程:

位置在a[0],這時a=[1, 2, 3, 4, 5]。移除a[0],也就是1,a變成[2, 3, 4, 5],位置a[0]處理完畢。

位置移到a[1],這時a=[2, 3, 4, 5]。移除a[1],也就是3,a變成[2, 4, 5],位置a[1]處理完畢。

位置移到a[2],這時a=[2, 4, 5]。a[2],也就是5,不用移除,a不變,還是[2, 4, 5],位置a[2]處理完畢。

a[2]已經處理完了,接下來應該處理a[3],但現在a=[2, 4, 5],沒有a[3],所以工作結束,for迴圈執行完畢。

從上面的分析可以知道,利用for迴圈來逐個走訪處理list的元素時,就只會一個位置一個位置依序地走訪處理過去,一個位置就只處理一次,不管已經處理過的那個位置中的內容,是不是有變動,絕不回頭處理第二次。這就是前面提到的,會造成有些粒子的狀態沒有被更新的原因。

造成問題的原因知道了,那要怎麼解決呢?解決的辦法,其實非常簡單,看是要用相反的順序來處理list的元素,或者是建立一個list的副本,然後檢查副本的元素而移除正本的元素,都可以。

用相反的順序來處理list的元素,也就是本來是由左至右依序處理,現在改成由右至左來處理,程式可以這樣寫:

for i in range(len(a)-1,-1,-1):
if a[i] <= 3:
a.remove(a[i])

因為會被移除的,都是已經走訪過的元素,所以也就不會有元素被漏掉沒處理到的問題。不過,這個寫法的缺點是不容易閱讀,而且萬一元素的處理順序不能改變,一定要由左至右處理,那就行不通了。

用建立副本的方式來寫,可以寫成這樣:

for i in a.copy():
if i<=3:
a.remove(i)

這裡的a.copy(),也可以改用a[:]。因為走訪的是副本的元素,而移除的是正本的元素,所以就不會有元素被漏掉沒處理到的問題。這個寫法比較容易閱讀,而且也不用擔心元素處理順序的問題。

解決了for迴圈邊走訪邊移除list元素的問題之後,應該馬上會想到另一個問題:那在for迴圈中,邊走訪邊增加list的元素,這樣會有問題嗎?這問題的答案是:會有問題,發作時的症狀會很明顯、很容易察覺,但是後果會很嚴重。欲知詳情,請看〈一個關於for loop和list的小實驗〉。

留言
avatar-img
留言分享你的想法!
Red-avatar-img
2023/12/20
還是很專業,呵^_^
ysf-avatar-img
發文者
2023/12/21
Red 😁
avatar-img
ysf的沙龍
15會員
141內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/04/14
花了些時間,靜下心來,仔仔細細地研究了一番,總算把Python呼叫函數時引數的傳遞方式給徹底搞清楚了。
2025/04/14
花了些時間,靜下心來,仔仔細細地研究了一番,總算把Python呼叫函數時引數的傳遞方式給徹底搞清楚了。
2024/05/08
呼!折騰了好久,終於徹底搞清楚pygame的各個blend mode所用的計算式,到底是長啥樣子了。
2024/05/08
呼!折騰了好久,終於徹底搞清楚pygame的各個blend mode所用的計算式,到底是長啥樣子了。
2023/10/15
用matplotlib畫正弦函數sin的圖形會有多難呢?應該是挺簡單的。不過,要畫得漂亮讓人滿意,還是非得費一番功夫調整不可。
Thumbnail
2023/10/15
用matplotlib畫正弦函數sin的圖形會有多難呢?應該是挺簡單的。不過,要畫得漂亮讓人滿意,還是非得費一番功夫調整不可。
Thumbnail
看更多
你可能也想看
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
提供一條簡單公式、一套盤點思路,幫助你快速算出去日本自助旅遊需要準備多少日幣現金!
Thumbnail
提供一條簡單公式、一套盤點思路,幫助你快速算出去日本自助旅遊需要準備多少日幣現金!
Thumbnail
在 Python 中,tuple 與 List有一個關鍵的不同點:tuple 是不可變的,這意味著一旦創建了 tuple,就無法更改其內容。 這與 List的可變性形成了對比,list 可以新增、刪除或修改元素。 元素的意思: 元素:指的是 List 中的每一個獨立的項目或值。
Thumbnail
在 Python 中,tuple 與 List有一個關鍵的不同點:tuple 是不可變的,這意味著一旦創建了 tuple,就無法更改其內容。 這與 List的可變性形成了對比,list 可以新增、刪除或修改元素。 元素的意思: 元素:指的是 List 中的每一個獨立的項目或值。
Thumbnail
Array可以說是各種語言除了基本型別之外,最常用的資料型別與容器之一了。 Array 這種連續格子狀的資料結構,在Python要怎麼表達呢? 建立一個空的陣列 最簡單也最直接的寫法就是 array = [] # Python list [] 就對應到大家熟知的array 陣列型態的資料結
Thumbnail
Array可以說是各種語言除了基本型別之外,最常用的資料型別與容器之一了。 Array 這種連續格子狀的資料結構,在Python要怎麼表達呢? 建立一個空的陣列 最簡單也最直接的寫法就是 array = [] # Python list [] 就對應到大家熟知的array 陣列型態的資料結
Thumbnail
在寫《The Nature of Code閱讀心得筆記——使用Python實作》的[第四章]4.3節時,原書提到,在使用Java的ArrayList時,如果用迴圈一面走訪一面又移除其中的元素,那會有難以察覺的問題存在。寫個小程式測試的結果發現,Python的list也會有一樣的問題。
Thumbnail
在寫《The Nature of Code閱讀心得筆記——使用Python實作》的[第四章]4.3節時,原書提到,在使用Java的ArrayList時,如果用迴圈一面走訪一面又移除其中的元素,那會有難以察覺的問題存在。寫個小程式測試的結果發現,Python的list也會有一樣的問題。
Thumbnail
陣列可以說是最常見的資料結構,LeetCode 裡的題目有過半都和 Array 有關,因此也通常是解題新手的第一站。在第一篇專文,我們就從它的操作方法講起。
Thumbnail
陣列可以說是最常見的資料結構,LeetCode 裡的題目有過半都和 Array 有關,因此也通常是解題新手的第一站。在第一篇專文,我們就從它的操作方法講起。
Thumbnail
JavaScript Array 基本操作筆記
Thumbnail
JavaScript Array 基本操作筆記
Thumbnail
雖然表面是一道移除元素的題目,但實際上考的還是搬移。 題目要求的是把val目標值移除,等價於 把非目標值的搬到前面,並且回傳這些剩下來的元素個數(也就是 陣列裡面,非目標值的元素總數​)。 請看下方範例,會更好理解。
Thumbnail
雖然表面是一道移除元素的題目,但實際上考的還是搬移。 題目要求的是把val目標值移除,等價於 把非目標值的搬到前面,並且回傳這些剩下來的元素個數(也就是 陣列裡面,非目標值的元素總數​)。 請看下方範例,會更好理解。
Thumbnail
List 清單 和 Tuple元組 清單在Python裡面非常的常用,大家一定要熟練這些基礎的元素。 在Python中,列表(List)是一種常用的資料類型,用於儲存一組有序的元素。列表是可變的(Mutable),這意味著你可以在列表中新增、刪除和修改元素。列表使用方括號 []
Thumbnail
List 清單 和 Tuple元組 清單在Python裡面非常的常用,大家一定要熟練這些基礎的元素。 在Python中,列表(List)是一種常用的資料類型,用於儲存一組有序的元素。列表是可變的(Mutable),這意味著你可以在列表中新增、刪除和修改元素。列表使用方括號 []
Thumbnail
探索Python學習筆記中列表的建立、存取和常用方法。從使用中括號定義列表到了解索引、新增、刪除、修改等操作,並介紹append、remove、count等常用方法。
Thumbnail
探索Python學習筆記中列表的建立、存取和常用方法。從使用中括號定義列表到了解索引、新增、刪除、修改等操作,並介紹append、remove、count等常用方法。
Thumbnail
本文會介紹靜態結構 - 串列(List)與動態結構 - 鏈結串列(Linked List)來實踐陣列的不同功能,如:刪除、計算元素個數與反轉。
Thumbnail
本文會介紹靜態結構 - 串列(List)與動態結構 - 鏈結串列(Linked List)來實踐陣列的不同功能,如:刪除、計算元素個數與反轉。
Thumbnail
  寫程式最重要的目標就是讓重複的工作交給電腦去執行,解放雙手和精力去做重複的事情,今天要介紹的迴圈就是可以讓電腦重複執行程式直到條件結束的方式。
Thumbnail
  寫程式最重要的目標就是讓重複的工作交給電腦去執行,解放雙手和精力去做重複的事情,今天要介紹的迴圈就是可以讓電腦重複執行程式直到條件結束的方式。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News