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

Exercism round 3(Elixir從零開始系列 10)(鼠年全馬鐵人挑戰 W11)

這是 Elixir 從零開始 系列 10 唷

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

前言

我們繼續再戰~練習得越多,就越熟悉!

Ex5: Bob

題目

Bob is a lackadaisical teenager. In conversation, his responses are very limited.

Bob answers ‘Sure.’ if you ask him a question.

He answers ‘Whoa, chill out!’ if you yell at him.

He answers ‘Calm down, I know what I’m doing!’ if you yell a question at him.

He says ‘Fine. Be that way!’ if you address him without actually saying anything.

He answers ‘Whatever.’ to anything else.

Bob’s conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English.

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
defmodule Bob do
def hey(input) do
cond do
String.trim(input) == "" -> "Fine. Be that way!"
shouting?(input) && question?(input) -> "Calm down, I know what I'm doing!"
shouting?(input) -> "Whoa, chill out!"
question?(input) -> "Sure."
true -> "Whatever."
end
end

defp upcase?(input), do: input == String.upcase(input)
defp contain_letter?(input), do: String.match?(input, ~r/[[:alpha:]]/)
defp question?(input), do: String.trim(input) |> String.ends_with?("?")
defp shouting?(input), do: contain_letter?(input) && upcase?(input)
end

重點提示

這題的重點是在做條件篩選,當遇到什麼條件時要做什麼事情。

依照題目的講解,我們來看 test case 可以分析出五個條件

  • 什麼話都沒有講的時候,要回答 "Fine. Be that way!"
  • 講話內容去掉空白且最後一個字是問號的時候,要回答 "Sure."
  • 講話內容都是大寫的時候,要回答 "Whoa, chill out!"
  • 講話內容都是大寫且最後一個字又是問號的時候,要回答 "Calm down, I know what I'm doing!"
  • 不是以上條件,要回答 "Whatever."

根據以上我們可以定義一些函式,例如 upcase?contain_letter?question? 等等,利用他們來排列組合以上五點條件,詳細內容就參考程解答吧

Ex6: Beer Song

題目

這題的題目很長很長…

Recite the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.

Note that not all verses are identical.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.

98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.

97 bottles of beer on the wall, 97 bottles of beer.
Take one down and pass it around, 96 bottles of beer on the wall.
...
2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Take it down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.

For bonus points

Did you get the tests passing and the code clean? If you want to, these are some additional things you could try:

  • Remove as much duplication as you possibly can.
  • Optimize for readability, even if it means introducing duplication.
  • If you’ve removed all the duplication, do you have a lot of conditionals? Try replacing the conditionals with polymorphism, if it applies in this language. How readable is it?

Then please share your thoughts in a comment on the submission. Did this experiment make the code better? Worse? Did you learn anything from it?

解答

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
32
33
34
35
36
defmodule BeerSong do
@doc """
Get a single verse of the beer song
"""
@spec verse(integer) :: String.t()
def verse(0) do
"""
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
"""
end

def verse(1) do
"""
1 bottle of beer on the wall, 1 bottle of beer.
Take it down and pass it around, no more bottles of beer on the wall.
"""
end

def verse(number) do
"""
#{number} bottles of beer on the wall, #{number} bottles of beer.
Take one down and pass it around, #{number - 1} bottles of beer on the wall.
"""
end

@doc """
Get the entire beer song for a given range of numbers of bottles.
"""
@spec lyrics(Range.t()) :: String.t()
def lyrics(default \\99..0) do
default
|> Enum.map(&verse/1)
|> Enum.join("\n")
end
end

重點提示

Enum.map 的定義如下

map(enumerable, fun)

Returns a list where each element is the result of invoking fun on each corresponding element of enumerable.

For maps, the function expects a key-value tuple.

Range 是個 enumerable,所以我們可以使用 map 這個函式,針對裡面所有的元素都來執行 fun,這邊也是一個典型的利用自我遞迴來取代迴圈的例子喔

參考資料

  1. https://hexdocs.pm/elixir/String.html#trim/1
  2. https://hexdocs.pm/elixir/String.html#upcase/2
  3. https://hexdocs.pm/elixir/String.html#match?/2
  4. https://hexdocs.pm/elixir/Enum.html#map/2
  5. https://hexdocs.pm/elixir/Enum.html#join/2