你的程式碼聞起來很臭嗎?不用擔心!大夫幫你...越治越臭

Exercism round 2(Elixir從零開始系列 09)(鼠年全馬鐵人挑戰 W10)

這是 Elixir 從零開始 系列 09 唷

這是 w3HexSchool 鼠年全馬鐵人挑戰 Week 10 唷

前言

寫練習題的好處多多,一來可以消化之前所學,二來更符合實際應用,其實就是『坐而思,不如起而行』

Ex3: Word Count

題目

Given a phrase, count the occurrences of each word in that phrase.

For example for the input “olly olly in come free

1
2
3
4
olly: 2
in: 1
come: 1
free: 1

Words are compared case-insensitively. The keys are lowercase.

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
defmodule WordCount do
@doc """
Count the number of words in the sentence.

Words are compared case-insensitively.
"""
@spec count(String.t()) :: map
def count(sentence) do
String.downcase(sentence)
|> String.split([" ", "_", ":", "&", "@", "%", "^", "$", ",", "!"], trim: true)
|> Enum.reduce(%{}, &word_add/2)
end

defp word_add(word, acc) do
Map.update(acc, word, 1, &(&1 + 1))
end
end

重點提示

首先介紹 |>,它會將左側的結果傳遞到右側,如果右側是函式,則是傳遞至函式的第一個參數,因此,第一步驟我們把 sentence 全部轉成小寫後利用 |> 傳遞給字串切割,split 會將字串切割後回傳列表(list)

Enum.reduce 定義如下

reduce(enumerable, acc, fun)

Invokes fun for each element in the enumerable with the accumulator.

The initial value of the accumulator is acc. The function is invoked for each element in the enumerable with the accumulator. The result returned by the function is used as the accumulator for the next iteration. The function returns the last accumulator.

Examples

1
2
Enum.reduce([1, 2, 3], 5, fn x, acc -> x + acc end)
11

在程式碼內的 Enum.reduce(%{}, &word_add/2) 此處解釋:

  1. 第一個參數 enumerablesplit 所吐出之列表,利用 |> 傳遞過來,會長的類似 [“aaa”, “bbb”, “ccc”]
  2. 第二個參數 acc%{} 是一個空的 map 用來儲存字母的數量,它同樣也是 accumulator 的初始值
  3. 第三個參數 fun 為函式 word_add/2,所要帶入的兩個參數就是 enumerableacc,會回傳 accumulator,也就是 map,這個 map(accumulator) 會被 word_add(Map.update) 不斷更新

Map.update 定義如下

update(map, key, initial, fun)

Updates the key in map with the given function.

If key is present in map with value value, fun is invoked with argument value and its result is used as the new value of key. If key is not present in map, initial is inserted as the value of key. The initial value will not be passed through the update function.

Map.update(acc, word, 1, &(&1 + 1)) 此處的解釋為從 accEnum.reduce 中的 accumulator) 這個 map 中找尋是否有 word 有的話就更新(利用函式 &(&1 + 1)),沒有的話就新增 wordacc 中,最後回傳 map(accumulator)

Ex4: Roman Numerals

題目

Write a function to convert from normal numbers to Roman Numerals.

The Romans were a clever bunch. They conquered most of Europe and ruled it for hundreds of years. They invented concrete and straight roads and even bikinis. One thing they never discovered though was the number zero. This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. For example the BBC uses Roman numerals to date their programmes.

The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice these letters have lots of straight lines and are hence easy to hack into stone tablets).

1
2
3
 1  => I
10 => X
7 => VII

There is no need to be able to convert numbers larger than about 3000. (The Romans themselves didn’t tend to go any higher)

Wikipedia says: Modern Roman numerals … are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero.

To see this in practice, consider the example of 1990.

In Roman numerals 1990 is MCMXC:

1000=M 900=CM 90=XC

2008 is written as MMVIII:

2000=MM 8=VIII

See also: http://www.novaroma.org/via_romana/numbers.html

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
defmodule RomanNumerals do

# module attributes
@mapping [
{1_000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"}
]

@doc """
Convert the number to a roman number.
"""
@spec numeral(pos_integer) :: String.t()
def numeral(0), do: ""
def numeral(number) do
# 檢查數字有沒有大於 list, 有的話則輸出該羅馬字並且數字相減, 重複此步驟
{match_value_x, match_value_y} = Enum.find(@mapping, fn {x, _} -> number >= x end)
# 使用 <> 來連接輸出之羅馬字
match_value_y <> numeral(number - match_value_x)
end
end

重點提示

這題比較簡單,就是考驗怎麼把迴圈的想法轉換成 Elixir 函式,這裡我們直接看註解就可啦

參考資料

  1. https://hexdocs.pm/elixir/Enum.html#reduce/3
  2. https://hexdocs.pm/elixir/Map.html#update/4
  3. https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2