發表文章

目前顯示的是有「APL」標籤的文章

Let's Split!

圖片
這是2020年度的Dyalog APL的其中一道題目在,此分享一下解答過程的思考歷程。 先看看題目: 簡單解說一下:寫出函數 F,右引數 Y 可以為向量或陣列,左引數 X 為任何非零的數字,任何情況下返回一個陣列 C。當 X 為正數時,C 的第一個元素包含 Y 的首 X 個元素,C 的第二個因素為餘下之 Y 的元素;當 X 為負數時,C 的第一個元素包含 Y 的尾 X 個元素,C 的第二個因素為餘下之 Y 的元素。 好似越講越複雜,還是看看上圖的示例比較直觀,反正就係 "split" 嘛。 過往,要不使用 if-else 邏輯的情況下同時處理向量、陣列輸入會有點困難。不過題目裏面貼心地提示可以使用 ↑ (take) 函數。而 ↑ 函數正好可以"乾淨地"解決這問題。示範一下 ↑、↓ 的用法: 3 ↑ Y 意思即從 Y 中提取 (take) 頭3個元素。當左引數為負數時,將從 Y 的末端取出相應數量元素: 而另一個相反效應的函數係 ↓ (drop),果效正如其名字: 以同一個數字 X 同時 take、drop 同一個陣列,就可達到分開陣列的效果: 不過題目還要求左引數為負數時,末段的元素要放置在輸出的前方。抽象一點理解,即係有條件地反轉上面輸出的陣列的兩個元素。於是之前介紹過的 ⍣ (power) 運算子便派上用場,用於有條件地呼叫 ⊖ (rotate) 函數一次: 由於 take、drop 本身可以處理向量及陣列輸入,上面的表達式同樣可以正確分隔向量成為單一因素的陣列,以及一個空陣列(否則過不了 Dyalog 網頁的 test case): 綜合上述後的答案是: {(⊖⍣(⍺<0)) (⍺↑⍵) (⍺↓⍵)}

簡單比較APL與Javascript

圖片
網上搜尋『陣列』的編程問題時,見到 Mozilla Developer Network 有Javascript的陣列操作例子。於是思考,用APL寫的話又會如何?以下係兩種語言的比較,其中Javascript的程式碼係直接取自MDN網頁: 陣列建構 &sol;&sol; Javascript let fruits = ['Apple', 'Banana'] ⍝ APL fruits←'Apple' 'Banana' 陣列索引 &sol;&sol; Javascript let first = fruits[0] ⍝ APL first←fruits[1] 另外在APL之中,也可以用函數 ⌷ 提取陣列元素: ⍝ APL first←1⌷fruits 以迴圈列印陣列元素 &sol;&sol; 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列的二維陣列,顯示出來便有垂直排列效果: 加入新元素至陣列末端 &sol;&sol; Javascript let newLength = fruits.push('Orange') APL newLength←≢fruits←fruits,⊂'Orange' 需要解釋一下APL語句...

Project Euler第八題

圖片
又係編程解題,先看題目: 題目出自 Project Euler第八題 ,要求找出以上1000位數字之中,任何連續13個數字的乘積的最大值。 這條題目早於2017年就已經解答過並提交了正確的答案。當時的APL程式: 毋庸置疑是一場災難,非常驚嘆自己可以寫出這樣笨的程式,也許出於當時對陣列編程的陌生吧。加上劣拙的英語註解,整片程式與一塊廢鐵無異。當時的思路大致如下: 將1000位數字以字串C儲存 字串C轉化位1000個元素的陣列D,D的每個元素相當於數字的每個位數 將陣列D轉化為12個76行 x 13列的二維陣列(因為1000可以被13整除76次) 再將二維陣列的每一行中的13個位數相乘 最大的乘積即為答案 時值2020年,再看到同一條題目,我想到的答案卻是: 程式碼可以大量簡化是因為當中使用了" / "運算子。在雙價 (dyadic) 使用下," / "運算子將左面的函數安插在右面陣列的元素之中,即係函式語言中的還原 (reduction): 以上的表達式,執行起來就好似以下的一樣: 由此可見使用" / "運算子可以大幅簡化程式碼之餘,卻不會犧牲可讀性。更強大的功能係,在函數左方加入數字引數,可以達致區間還原 (partitioned reduction) 如下: 第一個表達式之中,函數  +  並沒有用作還原整條陣列,而是對每三個順序元素還原一次,區間大小由左引數3決定,亦是題解  ⌈/13×/⍎¨C 中  13 ...

以APL計算黃金比例

圖片
APL 語言之中,有一個運算子"power"可以幫助計算數學中的黃金比例。 APL 表達式幾乎與數學定義一樣漂亮、簡潔: 沒錯,只有7個 unicode 字元的表達式,便計算出黃金比例。對數論稍有認識的同仁,也許知道黃金比例的連續分數是: (圖片連結自 維基百科 ) 其定義不太明顯地使用了遞迴,以一般編程語言表現的話大概離不開: def phi(N): return 1 + (1 % phi(N)) end 根據數論,無理數只能以無限連續分數表達,即上面圖示中的 "..." 應該無窮盡地延伸下去,才為之正確的黃金比例。而上述函數定義亦導致無限遞迴,定義上雖然沒有錯,但運作上會導致 stack overflow,很難說是正確的程式。 那麼,APL 是如何僅憑十個字元計算出黃金比例呢?或者首先應該問:APL 返回的值正確嗎?以下係 Wolfram 所給的黃金比例的小數約數: 1.618033988749894848204586834365638117720309179805762862135... 而 APL 給出的值(使用 NARS2000 實作): 1.618033988749894 可見小數點後16個位被截斷 (truncate) 了,而小數點後1至15位都脗合。先撇除電腦表達、顯示浮點值的複雜問題, 1.618033988749894應該是可接受的約數。 下一個問題:APL 表達式是如何達致遞迴?秘訣在於 ⍣ 稱呼作"power"的運算子。 表達式 {1+1÷⍵}⍣≡1 中, ⍣ 的作用是 呼叫 {1+1÷⍵} 1 結果為整數2,與原先的參數(整數1)以函數 ≡ 比較 2≡1 返回整數0亦即布林值false 如果上一步得出結果為布林值true,則以整數2為答案,否則繼續下一步 返回第一步,以整數2作為輸入 也許將 ≡ 函數替換成先把左、右參數列印的匿名函數會比較容易理解: ...

APL Code Golfing

圖片
APL實在是code golfing的利器。 今日突然想練一練code golf,馬上到 code-golf.io 看看,見到一道列印首二十行 Pascal Triangle 的題目,而排行第一的挑戰者居然使用了52個字元的答案。直覺告訴我APL應該可以更精煉,於是決定試試挑戰。 先公佈我的題解,只需要26個字元: 題目比預期中難,用了差不多半個小時仍然處理不好base case,即單一元素的陣列{1}。按下面來自維基的示範,當輸入為陣列V = {1, 3, 3, 1},應先將第1、2個元素相加並得出新元素1 + 3 = 4,如是者再重複相加第2、3及3、4個元素,得出新元素3 + 3 = 6與3 + 1 = 4: (圖片鏈結自 維基百科 ) 於是問題便轉化為產生陣列{1, 3, 3, 1}的指標的二元組(2-tuple) {1, 2},{2, 3},{3, 4}。用以存取陣列V的元素,並相加起來: 再費一些功夫抽取對角線上的元素{4, 6, 4}便可以了,感覺好像快將完成解答。 幾個想法突然湧現: 這樣的程式碼只是將程序式編程(procedural programming)的思維照搬不誤 程式碼極為欠缺美感,且勢必多於52個字元 致命的是,當陣列V為{1}的時候,會引致錯誤 原因係當V為{1}時, ⍴(¯1↓⍳⍴V) (1↓⍳⍴V) 的外積(outer product)是空的二維陣列,導致匿名函數  {+/V[⍺ ⍵]} 發生錯誤。縱使以常理推斷,陣列中沒有任何元素的時候,前述的函數應該不會被呼叫才對。例如python之中將必然拋出錯誤的lambda用於空陣列上: 再仔細思考,輸入陣列{1, 3, 3, 1}時只需要將輸入去掉頭、尾 (即{1, 3, 3}與{3, 3, 1})相加便可得出{4, 6, 4},再無條件地將{1}置於{4, 6, 4}的頭、尾,就可以得出Pascal Triangle的第五行{1, 4, 6, 4, 1}。 直觀地翻譯為APL,就得出題解使用的 匿名函數  {1,⍨1,(¯1↓⍵)+1↓⎕←⍵} ,最重要係當輸入為純量1或者陣列{1}時,輸出都會係{1, 1}。 不太理解為何其他參與者不用 J語言...

以APL模擬大話骰

圖片
上星期同幾個朋友去酒吧玩樂,興之所至玩起經典遊戲“大話骰”來。玩家總共有五人,其中一個朋友起手便叫“七個二”,雖則一點可以用作“百搭”,但酒後的直覺告訴我“七個二”應該是很進取的開局。於是我便要求開了。 結果我輸了。 事後我決定找出五人大話骰中“七個二”的概率,但我的所有概率知識都已經還給老師了,怎麼辦? 想了一會,用電腦模擬擲骰好了,省力不費時。用甚麼語言?除了APL,不作他選。 每局開始,5個人分別擲5粒骰,即係產生25個1至6的亂數: 每次模擬一局未免太沒有效率,但為了方便示範,暫時模擬五局好了: 用 二維陣列 裝載擲骰結果,每一“橫行”代表一局。先用變數D暫存結果,方便核對。 現在數算每局中有多少個一(“百搭”)或二點: 由於只模擬了五局,用人眼便可以印證以上結果正確。 那麼五局之中,有多少局出現等於、多於七個二? 所有結果都有多於、等於“七個二”。或者係運氣使然,所以才會完敗?按簡單機率理論,增加試驗次數 (trial) 應該會使結果趨向期望值 (expected value)。只要將變數D替換成很多局的結果便可以: 試驗一萬及十萬次,多於、等於“七個二”的概率分別得出78.47%和77.946%。 難怪我會壯烈犧牲。

用APL產生六合彩組合

圖片
姑且用生成六合彩投注號碼做第一個例子,感覺應該比較有趣和生活化。 説穿了就是要從1至49“隨機”挑選6個數字,實在簡單到不行。這裡使用 NARS2000 直譯器,安裝過程就省略了,打開後,輸入  6?49 到唯一一個看似Notepad的視窗中: 之後按 Enter,結果將會在下一行顯示: 實再言簡意賅到不得了。  6?49 的意思就是從1至49中抽取6個不同的元素。在此,  ? 係一個 函數 ,其左右兩邊各自有數字 引數   6 和  49 。由於 ? 使用左右引數,這稱之爲 ? 函數的 雙價 用法。所返回的是有6個元素的 陣列 。 既然有雙價就勢必有 單價 吧?不錯, 單價 使用下, ? 返回 1至49内的一個數字 純量 : 關於 純量 與 陣列 的區別與用法,往後會再探討。 另外,將左引數換成49的話,所得就是1至49的隨機排列 (permutation) 了: 試想想,以上功能如果用 Java / C / Python 編寫的話要如何處理?

APL相關資源

關於APL的資料,大多以英文爲主而鮮有中文的。簡單的Google搜尋一下“APL 語言 site:tw”或者“APL 語言 site:hk”便知道中文資料有多貧乏。 直譯器 https://tryapl.org/ 網上APL直譯器,免除安裝的麻煩,有virtual keyboard可以無痛輸入APL符號 https://www.dyalog.com/ 我初次使用的APL直譯器,需要安裝附帶的輸入法用以輸入APL符號 http://www.nars2000.org/ 我現在主要使用的APL interpreter,無需另外安裝輸入法 Youtube 短片 https://www.youtube.com/watch?v=_DTpQ4Kk2wA 1975年英國皇家學院示範使用"teletype"遙距使用APL。現代的人大概難以想像,當年使用電腦,係需要用一部打字機,通過電話綫連接遠端伺服器傳送指令,伺服器傳回結果再由打字機列印出來...... https://www.youtube.com/watch?v=DmT80OseAGs Dyalog APL的John Scholes示範用Dyalog APL解決數獨題目 https://www.youtube.com/watch?v=a9xAKttWgP4 同樣來自John Scholes,使用Dyalog APL編寫經典電腦"遊戲"Game of Life 學習資源(入門級) https://www.dyalog.com/uploads/documents/MasteringDyalogAPL.pdf Dyalog APL的電子書,亦係我最初使用的教材,適合絕對初學者 http://microapl.com/APL/LearningAPLwithAPLX.pdf MicroAPL的教材,以其產品APLX爲基礎。不過MicroAPL已被Dyalog APL拼購,APLX雖然可供下載,但不會再有更新 https://aplwiki.com/wiki/Main_Page 開放的APL維基,可惜仍然只有英語內容,非常值得閱讀。其中關於APL 慣用語 的一頁更加值得玩味 學習資源(進階) http://www.so...