Lua入門 その5 — ループ

目次

Unknown programmer's programming note.

<2022-05-04 水>

1 何をするか

続けて簡単な計算の問題をやっていきます。 手計算や電卓でもできそうなかなり簡単な計算の問題です。 しかし、問題を解くためには、Luaが提供する機能を一つ覚える必要があります。 それは、指定された回数だけ同じような処理をくりかえすように命令する ループ という機能です。 今回の問題は、ループを導入するためだけを目的としたもので、あえて簡単な問題にしてみました。

2 問題5

今回の問題は次の通りです。

問題5 — 与えられた範囲の数の合計

コマンドライン引数で、整数の下限と上限を受け取り、その範囲の整数の合計を求めて表示する。

例えば lua54 sum.lua 1 5 というコマンドでプログラムが起動されたら 1 + 2 + 3 + 4 + 5 の結果である 15 を求めて表示する。

2.1 作業場所を確保する

これまでと同じように、問題のための専用のディレクトリを用意します。

% cd ~/code/pintro⏎ ▹このシリーズ用のディレクトリに移動 ~/code/pintro は前回までで作成済とする
% mkdir problem5⏎   ▹この問題用のディレクトリを作成
% cd problem5⏎      ▹作成したディレクトリの中に移動
% pwd⏎              ▹カレントディレクトリのパスを表示
/home/toma/code/pintro/problem5

ユーザー名 toma のところは、自分のユーザー名に置き換えて下さい。

作業場所はこれで用意できました。

2.2 問題の吟味

問題自体は、これまでのものと比べても明快で理解しやすいものではないかと思います。 計算だけみると、足し算だけだけで事足ります。 しかし、これまでやったことだけでは不可能なことがあります。 もし、問題が「1から5までの整数の合計を求める」というものであったならば、簡単でした。

sum = 1 + 2 + 3 + 4 + 5
print(sum)

これで終りです。 ですが、下限と上限はコマンドラインの引数から取得することになっています。 これではどこからどこまでを足せばいいのか、直接コードに書くことはできません。 さらに言えば、たとえ範囲があらかじめ決められたものであっても、上のように直接足すべき数を書いていく方法では、1から1000まで、とかなったとき、直接1から1000までを記述するのは現実的ではありません。 したがって、別の方法を考える必要があります。

ここで、答えとなる必要なLuaの機能を紹介する前に、どのようにコードを書ければ問題を解決できるかを先に検討してみます。 今、コマンドライン引数で受け取った下限をlow、上限をhighという変数に格納したとします。 このとき、次のような記述ができればどうでしょう。

sum = low + (low + 1) + (low + 2) + ... + (high - 2) + (high - 1) + high

この記法はかなり分かりやすいのではないかと思います。

lowとhighの間隔が近すぎるとおかしくなるという問題は無視しておきます。

ポイントとなるのは「…」の部分で、これはある意味、式全体の意味を解釈して自動的に式に展開せよ、と指示していることととらえることができます。 数学の記法を使えばこれはもっとシンプルな形にできます。

\(\text{sum} = \displaystyle\sum_{i=low}^{high} i\)

\(\Sigma\)は「シグマ」と読み、総和を意味しています。

あまり覚えてないのですが、学校の数学では数列というところでこの \(\Sigma\) の記号が出てきたのではないかと思います。 そこではたぶん \(\sum_{i=1}^{k}a_i\) のような感じではなかったかと思います。 この場合は展開すると \(a_1 + a_2 + ... + a_{k-1} + a_{k}\) となり、数列aの各項を足すような意味になります。 今はiそのものを足したものが欲しいので、\(\Sigma\)の右にはiが直に置いてあります。

このようなきれいな形でコードに書ければいいのですが、残念ながらLuaの表現力はそこまで高くありません。 Luaだけでなく、ほとんどのプログラミング言語がこのように直接シグマの数式を記述することはできません。 プログラミング言語は、単に数学を記述するためだけのものではなく、もっと汎用的な、様々な現実の問題を解決するためのものであるので、数学の表記と直接対応させる必要性は必ずしもありません。 また、数学による記述は、人間のためのものであり、コンピューターに指示を出す表現としては適しているとも言い難いところがあります。 原理的には、コンピューターはもっと「こうこうこうして」と逐一指示を与えてやらないといけないものです。

では、どのようにして上の式をLuaで書けば良いかというと ループ を使うことができます。 ループとは繰り返しの意味です。 指定した回数だけ、同じような処理を繰り返します。 それがどうして合計を求める計算に繋るのかというと、コードを見た方が理解しやすいでしょう。 次のコードは1から5までの合計を求めるものです。

sum = 0           -- 合計を保持する変数。0をセットする
for i = 1, 5 do   -- iに初期値1をセットする。そして1ずつ増やしながら、5までループの本体を繰り返す
  sum = sum + i   -- ループの本体。sumにiを足して更新する。iには繰り返しごとに1、2、3、4、5という値がセットされてくる
end               -- ここがループの終り。ここまで来たら、forのところに戻る
print(sum)        -- 合計を表示する

forとdoの間のところがちょっと理解しづらいかもしれません。 コメントにも書きましたが、一応1ステップずつ見ていきます。

  1. まず i = 1 と書いてあるところが最初に評価されます。
  2. すると i という変数ができて、その値は1です。
  3. i が5と比較されます。もし i が5より大きければループは終了です。今 i は1なのでループの本体に進みます。
  4. ループの本体(doからendの間)が普通に実行されます。このとき i の値はループ毎に更新されていくということを利用して、同じ命令ながらも毎回違った結果を得ることができることが重要です。
  5. sum = sum + i は、現在のsumの値に i を足した値で、sumを更新しています。1回目のループの場合 sum = 0 + 1 ということになります。
  6. end に来たら、forのところに戻ります。
  7. i が1増やされます。
  8. i が5と比較されます。もし i が5より大きければループは終了です。 i が5を越えてなければ(6以上でなければ)もう一度ループの本体に進みます。
  9. sumを、現在のsumの値に現在の i の値を足して、更新します。
  10. endに来たら、forのところに戻ります。
  11. 7.から同じことを繰り返します。

ループが終了した時点で、sumには1から5までの合計が入っていることになります。 sumの変化だけに注目して見てみます。

  • ループ1回目(sum = 0, i = 1): sum = 0 + 1 => 1
  • ループ2回目(sum = 1, i = 2): sum = 1 + 2 => 3
  • ループ3回目(sum = 3, i = 3): sum = 3 + 3 => 6
  • ループ4回目(sum = 6, i = 4): sum = 6 + 4 => 10
  • ループ5回目(sum = 10, i = 5): sum = 10 + 5 => 15

このように、結果的に1から5までの合計が求まりました。

i の初期値と、終了値は数値をダイレクトに書かなければいけないという制限はありません。 変数を使うこともできます。 もし、lowとhighという名前の変数にコマンドライン引数で指定された値が入っているなら、もうほぼこの問題の解答に近いものになります。

low = tonumber(arg[1])
high = tonumber(arg[2])

sum = 0
for i = low, high do
  sum = sum + i
end
print(sum)

このコードをsum.luaというファイルに記述して実行します。

エラーチェックは省略しています。

もう一度さっきのforのところだけ見てみます。

sum = 0
for i = low, high do
  sum = sum + i
end

そして、これを前の方で書いた数学での表現と比較してみます。

\(\text{sum} = \displaystyle\sum_{i=low}^{high} i\)

どうでしょう、よく似ているとは思えないでしょうか。 数式を最初に提示したのは、この類似性に注意を向けたかったからです。 forによるループと、数学シグマというあまり関係もなさそうなものが、合計を求めるという問題に関してはきれいにシグマの数式を表現できています。

また、forによるループは、ただ総和を求めるというだけの処理に止まらず、極めて広い範囲の問題に対して適用が可能です。 まだごく基本的な使い方を紹介しただけです。 これから色んな使い方が登場してくると思います。

2.3 課題:コマンドライン引数のチェック

さっき書いたコードでは、コマンドライン引数が2つ与えられなかったときや、また数値として解釈できなテキストだった場合、エラーメッセージを吐き出して終了します。

% lua54 sum.lua            ▹引数を与えずに実行してみる
...エラーメッセージが表示される 
% lua54 sum.lua 1 abc      ▹数値として解釈できないテキストを引数として与えて実行してみる
...エラーメッセージが表示される

また、引数が数値であった場合でも、整数でなかった場合、動作はしますが期待したものとは違うものになるかもしれません。

% lua54 sum.lua 1.1 2.2    ▹小数を指定してみる 
3.2                        ▹これは期待した結果だろうか? 

こういった場合は、引数が不正であるということと、正しい使い方を表示して、計算は実行しないでおくのが親切です。 そのためには、引数が期待するものであるかどうか、想定内のものであるかどうかをチェックする必要があります。 ifを使って想定外のものであったら、メッセージを表示して終了してしまうというのが妥当でしょう。 今回のケースでは、そのような処理は難しくありません。 しかし、今回やりたかったことはforによるループだけなので、やらないでおきます。

3 まとめ

今回やったことは次の一点です。

  • forループの最も基本的な使い方を覚えました。

著者: watercat

Created: 2023-03-19 日 01:34

Emacs 27.1 (Org mode 9.3)

Validate