網頁開發/程式課程備份文。
Ruby
--------------------------
由於我這邊想在這份作業中加入undo功能,
研究了一下, 發現這是一個code本身不難, 但是概念很抽象的玩意
覺得蠻重要的, 所以把研究結果分享一下
如本篇挑戰題, 教案內提示的idea
球員資料可用陣列包雜湊的方式來保存
players = [
{name: "櫻木花道", pts: 0, reb: 14, ast: 0, stl: 0, blk: 2},
{name: "流川楓", pts: 30, reb: 6, ast: 3, stl: 3, blk: 0},
{name: "赤木剛憲", pts: 16, reb: 10, ast: 0, stl: 0, blk: 5},
{name: "宮城良田", pts: 6, reb: 0, ast: 7, stl: 6, blk: 0},
{name: "三井壽", pts: 21, reb: 4, ast: 3, stl: 0, blk: 0}
]
思考了一陣子undo的功能該如何實現,
想到了.... 如果再建立一個bake用陣列, 將上一筆資料保存在內
undo時再把bake內的資料傳過去, 應該就能實現了
但做實驗時卻出現超級神奇的現象....
data = ['a', 'b', 'c']
bake = data.clone #要加.clone, 兩者才會是不同obj
p bake.object_id == data.object_id # => false 確認是不同obj
#改變陣列第一個字串
bake[0][0] = 'z'
p data # => ["z", "b", "c"]
p bake # => ["z", "b", "c"] 神奇了, 居然一樣?
#換個方法改變值
bake[0] = 'k'
p data # => ["z", "b", "c"]
p bake # => ["k", "b", "c"] 更神奇了, 居然又不一樣了!?
What the....
看到這個神秘現象, 我還真以為Ruby壞掉了
就卡在這試了好久, 我覺得Ruby真的壞了
然後google一下發現,
原來copy有分淺拷貝(Shallow Copy)與深拷貝(Deep Copy)
參考:
裡面的老外一樣鬼打牆了, 然後另一個老外回答copy有分兩種
並建議他使用「Marshal」或是利用「.each」+「.dup」來進行深拷貝
參考
Object的頁面, 可直接Ctrl F找關鍵字「clone」, 「dup」
至於Marshal, 是Ruby自帶的一個marshaling library
雖然不太清楚這是啥意思, 但好像可以簡單理解成Ruby自帶的巨集
把很多指令打包成巨集, 命名為Marshal, 可以直接call Marshal來調用
說明文件前半部在解釋原理, 下半部才解釋怎麼用
至於到底甚麼是深拷貝與淺拷貝?
這篇文章有附圖來解釋, 很淺顯易懂↓
參考
淺拷貝, 只是把值指到拷貝對象, 共享同一個值 (應該是記憶體位置相同的概念)
深拷貝, 則是真的再複製出一份
這個如果是單純的變數 x = 0, 這種是看不出差異的
必須要是Array或是Hash才可以看出端倪
例如上方的舉例
data = ['a', 'b', 'c']
bake = data.clone
推測Ruby後台, Array的處理方式應該是「殼」+「值」
兩者各佔一個記憶體位置
進行淺拷貝時, bake會產生一個新的「殼」, 但是「值」共享
所以這兩者才會出現差異
bake[0][0] = 'z'
p data # => ["z", "b", "c"]
p bake # => ["z", "b", "c"]
bake[0] = 'k'
p data # => ["z", "b", "c"]
p bake # => ["k", "b", "c"]
bake[0][0], 是指定陣列第一格, 將內容的第0號字符「抽換」掉
實質上並沒有誕生新的值, data與bake的值仍然共享
bake[0], 一樣指定陣列第一格, 但是他將整個值都換掉
此時將誕生一個新的值, 造成data與bake的值不再共享
了解後台原理之後, 拉回到本課題建議的資料存儲方式
players = [ Hash ] (陣列包雜湊)
如果直接.dup它......
players_bk = player.dup
會產生與上述相同的問題,
它將 Array 與 Array 對co,
所以陣列的「殼」被拷貝了, obj id也不同
但裡面的「值」, 也就是「雜湊本身」仍然是被共享的狀態
所以需要使用迭代.each的方式, 才能真正實現「值」的深拷貝
players_bk = Array.new #得先宣告空陣列, 定義它是Array
players.each { |item| players_bk << players } # <<, 跟.push類似
希望能幫上碰到同樣困擾的人。
ps. 嫌麻煩可直接用Marshal, 但是它跑比較慢