Open4
学習記録:ElmでCodewars
[7 kyu] Strong Number (Special Numbers Series #2)
与えられた正の整数がstrong numberかどうかを判定する問題。Strong numberとは、各桁の数字の階乗の和が自身と等しくなるような数のこと。たとえば145はstrong numberである。
自分の解答
strong : Int -> String
strong n =
if (isStrong n) then
"STRONG!!!!"
else
"Not Strong !!"
isStrong : Int -> Bool
isStrong n =
String.fromInt n
|> String.toList
|> List.map (String.fromChar >> String.toInt >> Maybe.withDefault 0 >> factorial)
|> List.sum
|> (==) n
factorial : Int -> Int
factorial n =
case n of
0 ->
1
_ ->
n * factorial (n - 1)
学んだこと・振り返り
- パイプ演算子
|>
の使い方- 直前の値を1つ、パイプで繋いだ先の関数の引数として渡せる
- 🤔💭(2つ以上の値は渡せないのかな…?)
- ヘルパ関数への切り出し
- 切り出しの粒度が適切かどうかはまだ判断できない
-
Elm Guide 曰く、部分適用が長くなってたらヘルパ関数へ切り出した方が良いとのこと
-
isStrong
はちょっと長すぎかも…?
-
-
List.map
のelement-wiseな処理を関数合成>>
でまとめてしまったけど、一般的な手法なのかは不明
[6 kyu] Statistics for an Athletic Association
時間|分|秒
というフォーマットで表される複数のタイムの値域 Range
、平均 Average
、中央値 Median
を求めて、指定の文字列フォーマットで出力する問題。
自分の解答
stat: String -> String
stat s =
let
range = listSeconds s |> calcRange |> seconds2hms
average = listSeconds s |> calcAverage |> seconds2hms
median = listSeconds s |> calcMedian |> seconds2hms
in
if String.length s == 0
then
""
else
String.join " " ["Range:", range, "Average:", average, "Median:", median]
listSeconds: String -> List Int
listSeconds s = String.split "," s |> List.map (String.trim >> hms2seconds)
hms2seconds: String -> Int
hms2seconds hms =
let
conv s = String.toInt s |> Maybe.withDefault 0
in
String.split "|" hms
|> List.indexedMap (\i s -> if i == 0 then (conv s)*3600 else if i == 1 then (conv s)*60 else conv s)
|> List.sum
seconds2hms: Int -> String
seconds2hms sec =
let
h = sec // 3600
m = (modBy 3600 sec) // 60
s = modBy 60 sec
conv i = String.fromInt i |> String.padLeft 2 '0'
in
String.join "|" [(conv h), (conv m), (conv s)]
calcRange: List Int -> Int
calcRange list =
let
max = List.maximum list |> Maybe.withDefault 0
min = List.minimum list |> Maybe.withDefault 0
in
max - min
calcAverage: List Int -> Int
calcAverage list =
(List.sum list) // (List.length list)
calcMedian: List Int -> Int
calcMedian list =
let
len = List.length list
in
if modBy 2 len /= 0 then
List.sort list
|> List.drop (len // 2)
|> List.head
|> Maybe.withDefault 0
else
List.sort list
|> List.drop (len // 2 - 1)
|> List.take 2
|> calcAverage
学んだこと・振り返り
- let式の使い方
- 関数内の一時変数を作れるので、従来手続き型でコーディングしていた身には優しい仕様
- let式の中で定義した一時変数を同じlet式の別の行で使えるということは、let式の実行順序は上→下ということなのかな…?
- とても単純なことをしているだけのはずなのに、コード長くない? こんなもん…?
let式の実行順序は上→下ということなのかな…?
結論: 順序は関係ない(多分)
Elm REPLで検証
以下は簡単な例。もっと順序がクリティカルになる状況があるかもしれない。
> f =
| let
| a = b + 1 -- 後で定義する`b`をここで使う
| b = 2
| in
| a * b
|
6 : number -- ✅
> f =
| let
| c = d + 1 -- `c`と`d`を循環的に使う
| d = c + 1
| in
| c * d
|
-- CYCLIC VALUE ----------------------------------------------------------- REPL
I do not allow cyclic values in `let` expressions.
4| c = d + 1
^
The `c` value depends on itself through the following chain of definitions:
┌─────┐
│ c
│ ↓
│ d
└─────┘