exercism:一乗和の二乗と二乗和との差
概要
無料でプログラミングを学べる学習サイトexercismに、Gleamが追加されていました。
https://exercism.org/tracks/gleam/
この記事を書いている2023年2月26日時点では、78問の問題が用意されていました。
Gleamの技術同人誌[1]を書いているGleam推しの私ですので、復習も兼ねていくつか問題を解いてみました。
解いた問題の1つに「difference-of-squares」という問題がありました。
この問題が記憶に残ったので、自分が解いた内容をふりかえる意味もこめて記載してきます。
difference-of-squares
この問題は、自然数Nまでの"一乗和の二乗"と"二乗和"の差分を求めるプログラミング問題です。
ここで、"一乗和の二乗"とは、自然数Nまでの和を二乗すること。
また、"二乗和"とは、1からNまでの自然数をそれぞれ二乗した値の和を算出すること。
そして、それぞれ算出した値同志の差分を求めるものになります。
たとえば、N=10であった場合。
それぞれの算出値は以下の通り。
これらの差分を算出すると
問題の詳細については、こちらから。
https://exercism.org/tracks/gleam/exercises/difference-of-squares
では、実際に解いてみる
実際にこの問題を解いてみました。
エディタには、すでに各関数のテンプレートが用意されていました。
なお、すべての関数は、引数に自然数Nを取り、戻り値の型はIntとなっていました。
- 一乗和の二乗の値を返す関数: square_of_sum
- 二乗和の値を返す関数: sum_of_squares
- 一乗和の二乗から二乗和の差分を返す関数: difference
順番に解いていきます。
一乗和の二乗の値を返す関数
ポイントはつぎの通り。
- 自然数1からNまでの和を求める
- 求めた和を2乗する
和の産出については、1からNまで格納されたListを作成したのち、各Listの値をすべて加算できれば良さそう。
ということで、1からNまでのシーケンスに増えるListをlist.range/2
[2]で作成。
このListを引数にして、List(Int)
の合計値を算出するint.sum/1
[3]を利用します。
さて、和が算出できたので、つぎは二乗の値の算出。
二乗の値は、int.power/2
[4]を利用。ただし、算出値はFloat型になり、加えてResultでラップされている。
そのため、result.unwrap/2
[5]でResult型のラップを解除したあとで、FloatからIntへ変換でfloat.truncate/1
[6]を利用します。
と、いうことででき上がった関数はこちら。
import gleam/list
import gleam/int
import gleam/float
import gleam/result
pub fn square_of_sum(n: Int) -> Int {
list.range(1, n)
|> int.sum()
|> int.power(2.0)
|> result.unwrap(0.0)
|> float.truncate
}
二乗和の値を返す関数
ポイントはつぎの通り。
- 自然数1からNまでの値を二乗する
- 二乗した各々の値の和を求める
基本的な考え方、および利用する関数は"一乗和の二乗の値を返す関数"の場合と同じ。
ただし、今回はそれぞれの値を二乗するので、list.map/2
[7]を利用してList中の各値に対して無名関数を利用して二乗した値のListを作ります。
そして、二乗にはint.power/2
を利用したので、さらにresult.unwrap/2
とfloat.truncate/1
で加工して、Int型のListへ変換します。
さいごに、Int型のListをint.sum/1
して完了。
と、いうことででき上がった関数はこちら。
pub fn sum_of_squares(n: Int) -> Int {
list.range(1, n)
|> list.map(fn(x) { int.power(x, 2.0) } )
|> list.map(fn(x) { result.unwrap(x, 0.0) } )
|> list.map(fn(x) { float.truncate(x) } )
|> int.sum()
}
一乗和の二乗から二乗和の差分を返す関数
この関数については、上述の「一乗和の二乗の値」と「二乗和の値」の差分を求めるだけ。
pub fn difference(n: Int) -> Int {
square_of_sum(n) - sum_of_squares(n)
}
最終的にできたコード
まとめ。
上述の関数を組み合わせるので、以下の通り。
import gleam/list
import gleam/int
import gleam/float
import gleam/result
pub fn square_of_sum(n: Int) -> Int {
list.range(1, n)
|> int.sum()
|> int.power(2.0)
|> result.unwrap(0.0)
|> float.truncate
}
pub fn sum_of_squares(n: Int) -> Int {
list.range(1, n)
|> list.map(fn(x) { int.power(x, 2.0) } )
|> list.map(fn(x) { result.unwrap(x, 0.0) } )
|> list.map(fn(x) { float.truncate(x) } )
|> int.sum()
}
pub fn difference(n: Int) -> Int {
square_of_sum(n) - sum_of_squares(n)
}
テストを実行!
テストがすべてパス通過したので、この問題をクリアできました。
他の人の回答をみてみる
簡単な問題ではあったけど、標準ライブラリをそれなりに使用しての回答となりました。
個人的には、きれいにパイプラインによる連結ができたので満足はしているが、もっと違う方法で解けたのではないか、という疑問も出てきました。
exercismは、公開されていれば他のユーザーの回答も参照することが可能。
後学のためにも、他の方の回答をみておくか、と回答者の一覧をざっと眺める。
おっと!
Gleamの作者の"Louis Pilfold"氏の回答もみつけました。
やっぱり作者の方の解き方が一番勉強になるかな…と確認してみます。
pub fn square_of_sum(n: Int) -> Int {
let sum = n * { n + 1 } / 2
sum * sum
}
pub fn sum_of_squares(n: Int) -> Int {
n * { n + 1 } * { 2 * n + 1 } / 6
}
pub fn difference(n: Int) -> Int {
square_of_sum(n) - sum_of_squares(n)
}
……お、おう。
そうか、一乗和と二乗和の話なんだから、すでに公式がありましたね。
とか言いつつ、すっかり忘れていましたが。
なんというか、ギャップが強かったので、思わずTwetterでつぶやいてしまいました。
まとめ
今回の問題は、自分の技術同人誌にも記載していた基本的な関数を利用したものでした。
そして、問題文の内容(=仕様)を愚直に解いていった結果でき上がった回答。
個人的には、パイプで繋ぎ切れたのでそこまで嫌いではない。
しかし、そこまでゴリゴリと実装しなくても、解を導き出せるということに改めて気がつかされました。
やっぱり、他の人のコードは参考になるなぁ。
exercismにはGleamの問題はまだたっぷりとあります。
なので、これからも少しずつ問題を解いていき、Gleam力を身につけていきたいと思います。
Discussion