The Nature of Code閱讀心得與Python實作:4.3 A List of Particles

閱讀時間約 9 分鐘
這一節原書的標題是
4.3 An Array of Particles
因為改用List來處理,所以就把標題改成 A List of Particles

有了描述個別粒子的Particle類別之後,這一節就來看看要怎麼做,才能同時掌握許多粒子的動向,特別是這些粒子的數量是隨時都在變動的。

要同時處理數量不定的許多粒子,可以使用for迴圈搭配list這個資料結構,藉由listappend()remove()這兩個方法,就可以隨心所欲增加或減少粒子的數量。不過,在for迴圈裡頭操控list的元素得特別小心,不然可是會出亂子的。

先來看看在for迴圈裡頭使用listappend()方法,會有可能出現什麼問題。假設particles是個list,裡頭放了一些由Particle類別所製造出來出來的粒子。執行下面的程式之後,會發生什麼事呢?

for particle in particles:
particle.update()
particle.show()
particles.append(Particle(1, 320, 50))

執行上面那段程式之後會發現,那是個無窮迴圈,particles中的元素會越來越多!為什麼呢?因為每次執行particle.update()particle.show()處理完一個粒子之後,後面就會再生一個出來,等下一個處理完,它又會再冒一個出來;最後的結果就是:因為永遠有新的粒子生出來等著被處理,所以這個for迴圈就永遠跑不完。

上面那個例子很極端,發作時的症狀很明顯,很容易就會知道有問題。道理很簡單,因為粒子的數量會越來越多,吃掉的系統資源也會越來越多,最後會導致電腦不堪負荷而讓程式掛掉。接下來的這個例子,不會有這麼嚴重的後果,但卻因為症狀不明顯,而很容易被疏忽掉。

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

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

print(a)

答案是,會印出[2, 4, 5]。為什麼?到底發生了什麼事?

一開始的時候,也就是當我們位於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],也就是1a變成[2, 3, 4, 5],位置a[0]處理完畢。

位置移到a[1],這時a=[2, 3, 4, 5]。移除a[1],也就是3a變成[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的元素,或者是建立一個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[:]。這個寫法比較容易閱讀,而且也不用擔心元素處理順序的問題。

除了建立副本的寫法之外,也可以利用filter()函數來把要留下來的元素抽出來:

a = list(filter(lambda x: x>3, a))

這樣子寫,也就相當於是把不要的元素移除掉,只留下需要的元素。

下面這個不斷有粒子產生、消失的範例,是利用建立副本的方式來移除壽命已到的粒子。

Example 4.2: An Array A List of Particles

raw-image
# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 4.2: A List of Particles")

WHITE = (255, 255, 255)

screen_size = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

particles = []

gravity = pygame.Vector2(0, 0.05)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

particles.append(Particle(320, 50, 1))

for particle in particles.copy():
particle.apply_force(gravity)

particle.update()
if particle.is_dead():
# 移除壽命已到的粒子
particles.remove(particle)
else:
particle.show()

for particle in particles:
particle.apply_force(gravity)
particle.update()

for particle in particles:
particle.show()

pygame.display.update()
frame_rate.tick(FPS)

在移除壽命已到的粒子時,如果要用filter()來寫,那就要把for迴圈部分改成

for particle in particles:
particle.apply_force(gravity)
particle.update()

# 移除壽命已到的粒子
particles = list(filter(lambda particle: not particle.is_dead(), particles))

for particle in particles:
particle.show()

跟建立副本的寫法比較起來,這個寫法看起來簡潔、優雅多了。


15會員
120內容數
寫點東西自娛娛人
留言0
查看全部
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
你可能也想看
Google News 追蹤
Thumbnail
本專欄將提供給您最新的市場資訊、產業研究、交易心法、精選公司介紹,以上內容並非個股分析,還請各位依據自身狀況作出交易決策。歡迎訂閱支持我,獲得相關內容,也祝您的投資之路順遂! 每年 $990 訂閱方案👉 https://reurl.cc/VNYVxZ 每月 $99 訂閱方案👉https://re
Thumbnail
本文介紹了在進行資料分析時,將類別欄位轉換為數值欄位的方法,包括Label Encoding、One-Hot Encoding、Binary Encoding、Target Encoding和Frequency Encoding。每種方法的應用範例、優缺點和適用場景都有詳細說明。
這一節要來看看,有許多個力同時作用時,該怎麼處理。
Thumbnail
介紹pygame支援的向量運算,以及向量的減法、乘法、除法實際上是怎麼計算的。
ITS python認證內容含蓋六大主題
Thumbnail
【這個系列,目標是以比較輕鬆的方式讓大家一起學習AE表達式。】 本文是番外篇 3,主要是一些概念的補充,介紹陣列。
Thumbnail
Python 提供了一個功能豐富的標準函式庫,其中 random 專門用於生成隨機數。本文將介紹 random 的基本介紹,以及函式應用。
Thumbnail
宣告變數 變數是程式中用來儲存和表示數據的標識符號​,並將變數存放在某個記憶體位子 可以用ID的方法查找變數存在哪個記憶體,此方法有利於以後查找問題用。 在大多數程式語言中,變數需要事先聲明(宣告)並賦值。 而Python是一種動態類型語言,不需要顯式宣告變數類型,而是在賦值時自動進行推斷。
Thumbnail
本專欄將提供給您最新的市場資訊、產業研究、交易心法、精選公司介紹,以上內容並非個股分析,還請各位依據自身狀況作出交易決策。歡迎訂閱支持我,獲得相關內容,也祝您的投資之路順遂! 每年 $990 訂閱方案👉 https://reurl.cc/VNYVxZ 每月 $99 訂閱方案👉https://re
Thumbnail
本文介紹了在進行資料分析時,將類別欄位轉換為數值欄位的方法,包括Label Encoding、One-Hot Encoding、Binary Encoding、Target Encoding和Frequency Encoding。每種方法的應用範例、優缺點和適用場景都有詳細說明。
這一節要來看看,有許多個力同時作用時,該怎麼處理。
Thumbnail
介紹pygame支援的向量運算,以及向量的減法、乘法、除法實際上是怎麼計算的。
ITS python認證內容含蓋六大主題
Thumbnail
【這個系列,目標是以比較輕鬆的方式讓大家一起學習AE表達式。】 本文是番外篇 3,主要是一些概念的補充,介紹陣列。
Thumbnail
Python 提供了一個功能豐富的標準函式庫,其中 random 專門用於生成隨機數。本文將介紹 random 的基本介紹,以及函式應用。
Thumbnail
宣告變數 變數是程式中用來儲存和表示數據的標識符號​,並將變數存放在某個記憶體位子 可以用ID的方法查找變數存在哪個記憶體,此方法有利於以後查找問題用。 在大多數程式語言中,變數需要事先聲明(宣告)並賦值。 而Python是一種動態類型語言,不需要顯式宣告變數類型,而是在賦值時自動進行推斷。