簡單比較APL與Javascript
網上搜尋『陣列』的編程問題時,見到Mozilla Developer Network有Javascript的陣列操作例子。於是思考,用APL寫的話又會如何?以下係兩種語言的比較,其中Javascript的程式碼係直接取自MDN網頁:
陣列建構
// Javascript
let fruits = ['Apple', 'Banana']
⍝ APL
fruits←'Apple' 'Banana'
陣列索引
// Javascript
let first = fruits[0]
⍝ APL
first←fruits[1]
另外在APL之中,也可以用函數 ⌷ 提取陣列元素:
⍝ APL
first←1⌷fruits
以迴圈列印陣列元素
// Javascript
fruits.forEach(function(item, index, array) {
console.log(item, index)
})
⍝ APL
{⎕←fruits[⍵],⍵}¨⍳⍴fruits
⍝ APL again
⍪fruits,¨(⍳⍴fruits)
一如其他直譯式語言,在APL輸入表達式後都會列印出表達式的值。所以我提供第二個簡潔一點方案,即係將 fruits 及 ⍳⍴fruits 產生的指數陣列以函數 ,¨ 逐個配對,並以函數 ⍪ (1st axis catenate) 轉化為2行 x 1列的二維陣列,顯示出來便有垂直排列效果:
加入新元素至陣列末端
// Javascript
let newLength = fruits.push('Orange')
APL
newLength←≢fruits←fruits,⊂'Orange'
需要解釋一下APL語句的原理。執行此語句前,陣列 fruits 有兩個元素 "Apple" 及 "Banana"。要留意的是,"Apple" 及 "Banana" 本身就係由英文字母組成的陣列。作為陣列元素,它們被 "封裝" 成為APL的向量 (scalar),因此若以索引存取 fruits 的元素,所得的係被封裝後、維度係零的一個 "盒子",盒子內就是字串:
存取字串就需要使用函數 ⊃ (disclose) 將 "盒子" 解封:
所以新的元素 "Orange" 需要先被函數 ⊂ (enclose) 封裝後,再與原先的 fruits 連結。函數 ≢ (tally) 的功能自然係找出新的 fruits 的元素數量,即長度。
去掉最後一個元素
// Javascript
let last = fruits.pop()
⍝ APL
last←¯1↑fruits ⋄ fruits←¯1↓fruits
函數 ↓ (drop) 從陣列移除元素,負數1代表從末端移除一個因素。因為 ¯1↓fruits 只回傳新的陣列而非被 "pop" 走的元素,這裡先以 ¯1↑fruits 提取、保存最後一個元素,再更新 fruits。 "⋄" 係分隔陳述句的符號。
去掉第一個元素
// Javascript
let first = fruits.shift()
⍝ APL
first←1↓fruits ⋄ fruits←1↓fruits
與上一例雷同。
在陣列前端加入元素
// Javascript
let newLength = fruits.unshift('Strawberry')
⍝ APL
newLength←≢fruits←(⊂'Strawberry'), fruits
與加入新元素至陣列末端一樣,"Strawberry" 需要先被封裝,否則結果將會變成:
而非:
找出元素在陣列中的索引 (位置)
// Javascript
let pos = fruits.indexOf('Banana')
⍝ APL
pos←fruits⍳⊂'Banana'
函數 ⍳ 意思係index-of,同樣地,要搜尋的陣列元素係 ⊂'Banana'。假如去除 ⊂ 會導致甚麼的結果?姑且看看:
⍳ (index-of) 被輸入了陣列 "Banana",並會回傳 "Banana" 的元素在 fruits 之中的位置。顯然,字母 B、n、a 均不是 fruits 的元素,所以 ⍳ 回傳了索引陣列 [3, 3, 3, 3, 3, 3, 3],而3大於 fruits 的長度,表示遍尋不獲。
移除指定位置的元素
假定 fruits = ["Strawberry", "Banana", "Mango"],以Javascript 移除索引為1的 "Banana":
// Javascript
let removedItem = fruits.splice(pos, 1)
⍝ APL
fruits←((⍳⍴fruits)≠fruits⍳⊂'Banana')/fruits
解釋APL的運作需要更長的敘述。根據上一例可知 fruits⍳⊂'Banana' 回傳 "Banana" 的位置,即數字2 (APL的陣列索引始於1而非0)。⍳⍴fruits 產生一條與 fruits 長度相同的指數陣列 [1, 2, 3],函數 ≠ 將之與2比較,得出布林 (boolean) 陣列 [1, 0, 1]。通過函數 "/" 用布林陣列篩選 fruits,餘下元素 ["Strawberry", "Manago],並重新指定至 fruits。
從指定位置移除N個元素
假定 vegetables = ['Cabbage', 'Turnip', 'Radish', 'Carrot'],從位置1移除兩個元素:
// Javascript
let pos = 1
let n = 2
let removedItems = vegetables.splice(pos, n)
console.log(vegetables)
// ["Cabbage", "Carrot"] (the original array is changed)
⍝ APL
vegetables←vegetables[(⍳⍴vegetables)~1+⍳2]
(⍳⍴vegetables) 產生索引陣列,而 1+⍳2 產生數字陣列 [2, 3],並將之從 (⍳⍴vegetables) 中移除。得出陣列 [1, 4],以此達致 Javascript splice 的效果。
深複製 (Deep copy)
這裡恐怕MDN也犯了錯誤,意思應該係 deep copy 而非變量名稱所指的 shallow copy。
// Javascript
let shallowCopy = fruits.slice()
⍝ APL
deepCopy←fruits
APL的變量指定只有值的指定,一般情況下並沒有 shallow copy,部分支援物件編程的實作除外。
至此完成了MDN上"基本操作"的部分。
留言
張貼留言