Skip to content

2023

Advent of code

一年一度的 Advent of Code 又開始了,今年是我第一次參加,正好用來練習 Elixir。

Advent of Code 是馬拉松競賽,連續 25 天每天都有一個問題,每天的問題都會有兩個部分,第一部分的問題比較簡單,第二部分的問題會比較難,而且第二部分的問題會跟第一部分的問題有關聯。相對其他程式競賽,Advent of Code 有一個地方很特別,那就是他第二部份的問題的 Input 跟 第一部份是相同的,只是題目會比較難。 做了幾天題目之後,我在解第一部份的問題時,會先猜猜看第二部份的問題會是什麼,然後再來寫第一部份的程式,這樣第二部份的問題就會比較好寫了。

以下分享一些解題心得。

Day 3

Part 1

觀察到每個 row 可以獨立計算,利用兩層迴圈,第一層迴圈是 row,第二層迴圈是 column,從左到右,遇到符號的時候,就把數字記起來。比如說,row的值是

..123*..
. => 0
. => 0
1 => 1
2 => 12
3 => 123
* => 123

當讀到*的時候,就知道把數字是123,因為123旁邊有符號,所以把這個數字記起來。

Part 2

跟 Part 1 一樣,只是要多做一件事情,就是紀錄每個符號連結到的數字有哪些,最後找出連結到兩個數字的符號,然後把這兩個數字相乘。

Day 3 的 source 在 GitHub 上,有興趣的朋友可以看看。

Datascience elixir

Elixir 也很適合資料科學的應用,這篇文章會介紹 Elixir 的資料科學生態系統。

首先我們快速對比 Python 與 Elixir 常用的工具:

Python Elixir
Notebook Livebook
Numpy Nx
Pandas Explorer

Learning elixir

第 N 次學習 Functional Programming Language 就上手。

不論是上古神獸 Lisp,Haskell,或是現代一點的 Scala,如果你跟我一樣嘗試過學習 Functional Programming Language,但是都沒有成功,那麼 Elixir 是你最好的選擇。

Pipe Operation - 括號終結者

我朋友 ypcat 推我入 Elixir 的坑的時候,舉出了一個 Elixir 的優點,那就是 Elixir 的語法很簡單,很像 Ruby,所以學習起來很容易,最重要的是可讀性很高,沒有一堆括號()。

比如說我們要把字串的前後空白去掉,再轉成大寫,然後把 WORLD 換成 Gary,如果用 Python 寫的話,會是這樣:

astr = " Hello world! "
astr.strip().upper().replace("WORLD", "Gary")

這邊可以看到由於 Python 支援 object ,所以字串有 strip, upper, replace 這些 method 可以用,而且這些 method 都會回傳一個新的字串,所以可以一直串起來用。

再來看一下 Elixir 的寫法:

astr = " Hello world! "
String.trim(String.replace(String.upcase(astr), "WORLD", "Gary"))

巢狀的括號看起來是不是很難懂? 這時候就可以用到 Pipe Operation 來改寫:

astr = " Hello world! "
astr |> String.trim() |> String.upcase() |> String.replace("WORLD", "Gary")

背後的原則是,我們可以把這樣的 expression g(f(x)) 改寫成 x |> f() | > g() ,這樣看起來是不是清爽多了?

Thinking Elixir 入門課程

初次學習 Elixir 的朋友, 推薦從這邊開始,有兩堂入門的課程: ThinkingElixir

如果你還沒有 Elixir 的環境,建議可以從 Livebook 開始,Livebook 是一個可以在瀏覽器上執行 Elixir 程式的環境,可以很快的上手 Elixir 的語法。

Elixir 的資料結構

先來比較一下 python 跟 Elixir 的資料結構,特別注意 Elixir 是 functional programming language,所以資料結構都是 immutable 的, 而且沒有 object method,不管是 List 或是 Map 存取都是用 function。

List

Elixir 的 List 跟 Python 的 List 很像,但是同樣的 Elixir 的 List 是 immutable 的,如果修改 List 內的值,會得到新的 List 。 另外 Elixir 的 List 不像是 Array,不能直接存取第 n 個元素。一般 List 的用途是要搭配Pattern,例如 [head | tail] ,之後再說明。

Tuple

Python 的 Tuple 跟 Elixir 的 Tuple 很像,但是 Elixir 的 Tuple 是 immutable 的。

# Python
data = (1, 2, "GO") # Python
data[0] # 1
data[2] # "GO"

# Elixir
data = {1, 2, "GO"}
elem(data, 0) # 1
elem(data, 2) # "GO"
put_elem(t, 2, "foo") # 修改第二個值會傳回一個新的 tuple {1, 2, "foo"}

Map

Elixir 的 Map 跟 Python 的 Dictionary 很像,但是同樣的 Elixir 的 Map 是 immutable 的,如果修改 Map 內的值,會得到新的 Map 。

Python: Dictionary

data = { "name" : "gary" , "age": 99 }

Elixir: Map

產生 Map

data = %{ name: "gary", age: 99, city: "Tokyo" }

注意這裡的 name 是一個 atom (:name)

讀取 Map

data[:name]

寫入 Map

Map.put(data, :name, "bob")

更新 Map

%{data | age: 31, city: "Taipei"}