M.Hiroi's Home Page

お気楽 Lua プログラミング超入門

Lua の基礎知識 (制御構造編)


Copyright (C) 2011-2019 Makoto Hiroi
All rights reserved.

はじめに

Lua の制御構造を簡単に説明します。

●条件分岐

条件分岐には if を使います。Lua の if は真偽の判定が他のスクリプト言語と少し異なります。Lua の場合、真偽を表すデータ型 (boolean) として true と false が用意されていますが、nil と false を偽と判定し、それ以外の値を真と判定します。数値 0 や空文字列 "" も真と判定されるので注意してください。if の構文を示します。

if test then 処理A [else 処理B] end
        ↓                        ↓
  ┌─────┐偽          ┌─────┐偽
  │  条  件  │─┐        │  条  件  │─────┐
  └─────┘  │        └─────┘          │
        ↓真      │              ↓真              ↓
  ┌─────┐  │        ┌─────┐    ┌─────┐
  │  処理A  │  │        │  処理A  │    │  処理B  │
  └─────┘  │        └─────┘    └─────┘
        │        │              │                │
        ├←───┘              ├←───────┘
        ↓                        ↓

       (1)                        (2)

                    図 1 : if 文 (その1)
if test_a then 処理A elseif test_b then 処理B else 処理C end
        ↓
  ┌─────┐偽
  │  test_a  │─────┐
  └─────┘          │
        ↓真              ↓
        │          ┌─────┐偽
        │          │  test_b  │─────┐
        │          └─────┘          │
        │                ↓真              ↓
  ┌─────┐    ┌─────┐    ┌─────┐
  │  処理A  │    │  処理B  │    │  処理C  │
  └─────┘    └─────┘    └─────┘
        │                │                │
        ↓                ↓                ↓
        ├────────┴────────┘
        ↓

                図 2 : if 文 (その2)

条件部 test を実行し、その結果が真であれば 処理A を実行します。そうでなければ 処理B を実行します。then と else, else と end の間には複数の処理を記述することができます。これを「ブロック (block)」といいます。1 行に複数の処理を記述する場合はセミコロン ( ; ) で区切ります。Lua の場合、do 処理A; 処理B; ... end のように do と end でブロックを明示することができます。なお、上図 (1) のように else ブロックは省略することができます。

また、elseif を使うことで、if を連結することができます。test_a が偽の場合は、次の elseif の条件 test_b を実行します。この結果が真であれば処理B を実行します。そうでなければ、else ブロックの処理C を実行します。elseif はいくつでもつなげることができます。

●比較演算子と論理演算子

Lua には表 3 に示す比較演算子が用意されています。

表 3 : 比較演算子
演算子意味
== 等しい
~= 等しくない
<より小さい
>より大きい
<=より小さいか等しい
>=より大きいか等しい

Lua は数値だけではなく、文字列の比較にも用いることができます。データ型が異なる場合は異なる値と判断されます。型変換は行われないので注意してください。

簡単な例を示しましょう。

> 1 < 2
true
> 1 > 2
false
> 1 == 1
true
> 1 ~= 1
false
> 1 == "1"
false
> "abc" < "def"
true
> "abc" > "def"
false
> "abc" == "abc"
true
> "abc" ~= "abc"
false

Lua には表 4 に示す論理演算子があります。

表 4 : 論理演算子
操作意味
not x x の否定(真偽の反転)
x and y x が真かつ y が真ならば真
x or y x が真または y が真ならば真

簡単な使用例を示しましょう。

> not true
false
> not false
true
> true and true
true
> true and false
false
> true or true
true
> true or false
true
> false or true
true
> false or false
false

論理積 and は、左項が偽ならば右項を評価せずに偽を返します。論理和 or は、左項が真ならば右項を評価せずに真を返します。

●繰り返し

繰り返しは同じ処理を何度も実行することです。まずは簡単な繰り返しから紹介しましょう。

while test do 処理A; 処理B; ... end
              ↓
              ├←─────┐
      偽┌─────┐      │
┌───│  条件部  │      │
│      └─────┘      │
│            ↓真          │
│      ┌─────┐      │
│      │  処理A  │      │
│      └─────┘      │
│            ↓            │
│      ┌─────┐      │
│      │  処理B  │      │
│      └─────┘      │
│            ↓            │
│      ┌─────┐      │
│      │  処理C  │      │
│      └─────┘      │
│            ↓            │
│            └──────┘
└──────┐
              ↓

        図 3 : while 文

while 文は test が真であるあいだ、ブロック内の処理を繰り返し実行します。

簡単な例を示しましょう。hello, world を 10 回表示します。

> n = 0
> while n < 10 do print("hello, world"); n = n + 1 end
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world

print はデータを標準出力 (画面) に出力する関数です。print はその後に改行を行います。変数 n を 0 に初期化し、n の値が 10 よりも小さいあいだ処理を繰り返します。n の値はブロックを実行するたびに +1 されていくので、n が 10 になった時点で繰り返しを終了します。

このほかに、repeat - until 文もあります。

repeat 処理A; ...; 処理Z until test

while 文と違って、repeat - until 文は最初にブロック内の処理を実行し、test が偽であるあいだブロックを繰り返し実行します。つまり、test が繰り返しの終了条件を表します。簡単な例を示します。

> n = 0
> repeat print("hello, world"); n = n + 1 until n == 10
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world

次は for 文を説明します。Lua の for 文は数値用と汎用の 2 通りの形式があります。ここでは数値用の for 文を説明します。

for 変数名 = 式1, 式2, 式3 do 処理A; ...; 処理Z end
                ↓
          ┌─────┐
          │  初期化  │
          └─────┘
                ├←─────┐
    false ┌─────┐      │
  ┌───│  条件部  │      │
  │      └─────┘      │
  │            ↓true        │
  │      ┌─────┐      │
  │      │  処理A  │      │
  │      └─────┘      │
  │            ・            │
  │            ・            │
  │      ┌─────┐      │
  │      │  処理Z  │      │
  │      └─────┘      │
  │            ↓            │
  │      ┌─────┐      │
  │      │ 更新処理 │      │
  │      └─────┘      │
  │            └──────┘
  └──────┐
                ↓

        図 4 : for 文 (その1)

式 1 が初期値 (初期化)、式 2 が終了値 (条件部)、式 3 が増分値 (更新処理) を表します。式 3 を省略すると増分値は 1 になります。式 1, 2, 3 の値が m, n, a とすると、m, m + a, m + 2 * a, ... の順で変数に値をセットしてブロックを繰り返し実行します。変数の値が n より大きくなると繰り返しを終了します。

簡単な使用例を示しましょう。

> for n = 1, 10 do print(n) end
1
2
3
4
5
6
7
8
9
10
> for n = 1, 10 do print("hello, world") end
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world
hello, world

for n = 1, 10 do ... end は n を 1 に初期化し、n の値を +1 しながらブロックの処理を繰り返します。n が 11 になると終了値 10 よりも大きくなるので、繰り返しを終了します。

●繰り返しの制御

while 文, repeat 文、for 文は break 文によって繰り返しを脱出することができます。ただし、Lua には contiune 文がありません。ご注意ください。break 文の動作を図 5 に示します。

while test_a do
    処理A
    処理B
    if(test_c) then
        break  ──────┐
    end                    │
    処理C                  │
end                        │
 ←────────────┘
処理D

図 5 : break 文の動作

test_c が真で break 文が実行されると、それ以降の処理を実行せずに while 文, repeat 文, for 文の繰り返しを脱出します。図 5 では、break 文で while 文の繰り返しを脱出すると、while 文の次の処理 D が実行されます。

Lua の場合、break 文はブロックの最後にしか書くことが許されません。ブロックの途中で break 文を使いたい場合は、do break end のように新しいブロックの中で break を使ってください。

●配列とハッシュの操作

配列は [ ] を使ってアクセスするだけではありません。Lua には便利な関数や演算子が用意されています。主な操作を表 5 に示します。

表 5 : 配列に適用できる主な操作
操作意味
#ary配列の大きさを求める
table.insert(ary, [pos,] x)pos 番目に要素を追加する
pos が省略された場合は末尾に追加する
table.remove(ary, [pos,])pos 番目から要素を削除する
pos が省略された場合は末尾から削除する

ary は配列を表します。配列の操作関数はハッシュ table にまとめられています。ハッシュ内の関数は "ハッシュ名" + "." + "関数名" でアクセスすることができます。つまり、関数名がハッシュのキーで、値が関数の実体になります。

# は長さ演算子です。長さ演算子は添字 n の値が nil 以外で、かつ n + 1 の値が nil となる n を返します。Lua の場合、nil は値がないことを表すデータです。配列の要素に nil をセットすると、その要素はなくなったことになります。つまり、配列の途中で穴をあけた状態になるのです。このような状態を作らなければ、長さ演算子で配列の大きさを求めることができます。

簡単な例を示します。

> a = {}
> #a
0
> table.insert(a, 1)
> a[1]
1
> table.insert(a, 2)
> a[2]
2
> table.insert(a, 3)
> a[3]
3
> table.insert(a, 4)
> a[4]
4
> #a
4
> table.remove(a)
4
> table.remove(a)
3
> table.remove(a)
2
> table.remove(a)
1
> #a
0

ハッシュのキーと値は for - in 文で求めることができます。

 for 変数, ... in 式 do 処理A; ...; 処理Z end
              ↓
              ├←─────┐
     No ┌─────┐      │
┌───│要素がある│      │
│      └─────┘      │
│            ↓Yes         │
│   ┌────────┐   │
│   │変数 ← 次の要素│   │
│   └────────┘   │
│            ↓            │
│      ┌─────┐      │
│      │  処理A  │      │
│      └─────┘      │
│            ・            │
│            ・            │
│            ・            │
│      ┌─────┐      │
│      │  処理Z  │      │
│      └─────┘      │
│            └──────┘
└──────┐
              ↓

        図 6 : for 文 (その2)

in の後ろの式には「イテレータ (iterator)」またはイテレータを生成する関数を指定します。イテレータはデータ構造 (テーブルなど) の要素を順番にアクセスしていく関数です。関数 pairs はテーブルからキーと値を順番に返すイテレータを生成します。

for k, v in pairs(ary)  do 処理A; ...; 処理Z end
for i, v in ipairs(ary) do 処理A; ...; 処理Z end

for - in 文は pairs が生成したイテレータを呼び出し、その返り値を変数 k, v にセットして、ブロックの処理を繰り返し実行します。キーは変数 k に、値は変数 v にセットされます。なお、pairs のかわりに関数 ipairs を使うと、配列の添字と値を順番に取り出すことができます。

簡単な例を示します。

> a = {foo=10, bar=20, baz=30}
> for k, v in pairs(a) do print(k); print(v) end
baz
30
bar
20
foo
10

> b = {10,20,30}
> for k, v in ipairs(b) do print(k); print(v) end
1
10
2
20
3
30

●平均値を求める

それでは簡単な例題として、配列に格納されているデータの平均値を求めてみましょう。次のリストを見てください。

リスト 1 : 平均値を求める

height = {
  148.7, 149.5, 133.7, 157.9, 154.2, 147.8, 154.6, 159.1, 148.2, 153.1,
  138.2, 138.7, 143.5, 153.2, 150.2, 157.3, 145.1, 157.2, 152.3, 148.3,
  152.0, 146.0, 151.5, 139.4, 158.8, 147.6, 144.0, 145.8, 155.4, 155.5,
  153.6, 138.5, 147.1, 149.6, 160.9, 148.9, 157.5, 155.1, 138.9, 153.0,
  153.9, 150.9, 144.4, 160.3, 153.4, 163.0, 150.9, 153.3, 146.6, 153.3,
  152.3, 153.3, 142.8, 149.0, 149.4, 156.5, 141.7, 146.2, 151.0, 156.5,
  150.8, 141.0, 149.0, 163.2, 144.1, 147.1, 167.9, 155.3, 142.9, 148.7,
  164.8, 154.1, 150.4, 154.2, 161.4, 155.0, 146.8, 154.2, 152.7, 149.7,
  151.5, 154.5, 156.8, 150.3, 143.2, 149.5, 145.6, 140.4, 136.5, 146.9,
  158.9, 144.4, 148.1, 155.5, 152.4, 153.3, 142.3, 155.3, 153.1, 152.3
}

s = 0.0
for i = 1, #height do
  s = s + height[i]
end
print(s / #height)

配列 height には生徒 100 人の身長が格納されています。平均値を求めるには、これらのデータの合計値を求めて、それを人数で割り算すればいいですね。プログラムは簡単です。変数 s にデータの合計値を求めます。s は 0.0 に初期化しておきます。あとは for ループで配列 height の要素を順番に取り出して、それを変数 s に加算していきます。最後に s / #height を計算すれば平均値を求めることができます。

それでは実行してみましょう。

$ lua5.3 mean.lua
150.627

●数値積分

次は数値積分で円周率 \(\pi\) を求めてみましょう。区間 [a, b] の定積分 \(\int_a^b f(x)\,dx\) を数値的に求めるには、区間を細分して小区間の面積を求めて足し上げます。小区間の面積を求める一番簡単な方法は長方形で近似することです。この場合、3 つの方法が考えられます。

  1. (b - a) * f(a)
  2. (b - a) * f(b)
  3. (b - a) * f((a + b) / 2)

1 は左端の値 f(a) を、2 は右端の値 f(b) を、3 は中間点の値 f((a + b) / 2) を使って長方形の面積を計算します。この中で 3 番目の方法が一番精度が高く、これを「中点則」といいます。このほかに、台形で近似する「台形則」や、2 次近似で精度を上げる「シンプソン則」という方法があります。

それでは実際に、中点則で \(\pi\) の値を求めてみましょう。\(\pi\) は次の式で求めることができます。

\( \pi = \displaystyle \int_0^1 \dfrac{4}{1 + x^2}\,dx \)

プログラムは次のようになります。

リスト 2 : 数値積分で円周率を求める (midpoint.lua)

n = 10000
w = 1.0 / n
s = 0.0
for i = 1, n do
  x = (i - 0.5) * w
  s = s + 4.0 / (1.0 + x * x)
end

print(s * w)

変数 n が分割数です。最初に小区間の幅を求めて変数 w にセットします。面積は変数 s にセットします。次の for ループで区間 [0, 1] を n 個に分割して面積を求めます。

最初に x 座標を計算します。中間点を求めるため、変数 i を 1 から始めて、x 座標を次の式で求めます。

x = (i - 0.5) * w

たとえば、変数 i が 1 の場合は 0.5 になるので、x は区間 [0 * w, 1 * w] の中間点になります。あとは、4 / (1 + x * x) を計算して s に加算します。最後に s に w を掛け算して全体の面積を求めます。

実行結果を示します。

$ lua5.3 midpoint.lua
3.1415926544231

●素数を求める

最後にもう一つ簡単な例題として、素数を求めるプログラムを作ってみましょう。いちばん簡単な方法は、奇数 3, 5, 7, 9, ...をそれまでに見つかった素数で割ってみることです。見つけた素数は配列に格納しておけばいいでしょう。プログラムをリスト 3 に示します。

リスト 3 : 素数を求める (1)

prime_list = {2}

for x = 3, 100, 2 do
  a = true
  for y = 1, #prime_list do
    z = prime_list[y]
    if x % z == 0 then
      a = false
      break
    end
  end
  if a then
    table.insert(prime_list, x)
  end
end

for x = 1, #prime_list do
  io.write(prime_list[x])
  io.write(" ")
end

変数 prime_list は素数の配列で {2} に初期化します。奇数の生成は for 文を使うと簡単です。変数 x を 3 に初期化し、更新処理で +2 していけばいいわけです。次の for 文で prime_list から素数を取り出して変数 z にセットします。

ここで変数 a の使い方がポイントになります。最初に a を true に初期化しておきます。x % z が 0 ならば x は素数ではないので、a を false に書き換えてから break します。そして、for ループが終了した後、変数 a をチェックして true であれば x を insert で prime_list に追加します。最後 に for 文で prime_list を表示します。write はデータを表示する関数で、print とは違って改行を行いません。

それでは実行してみましょう。プログラムをファイル prime.lua に保存して、コマンドプロンプトで lua prime.lua を実行してください。

$ lua5.3 prime.lua
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

100 以下の素数は全部で 25 個あります。

ところで、この方法には無駄があります。x が素数か判別するため、x より小さい素数で割り切れるか調べていますが、実は \(\sqrt x\) より小さい素数を調べるだけでいいのです。次のリストを見てください。

リスト 4 : 素数を求める (2)

prime_list = {2}

for x = 3, 100, 2 do
  a = true
  for y = 1, #prime_list do
    z = prime_list[y]
    if z * z > x then
      break
    end
    if x % z == 0 then
      a = false
      break
    end
  end
  if a then
   table.insert(prime_list, x)
  end
end

for x = 1, #prime_list do
  io.write(prime_list[x])
  io.write(" ")
end

z > \(\sqrt x\) のかわりに z * z > x をチェックし、真であれば break で for 文を脱出します。これでリスト 3 よりも高速に素数を求めることができます。

ところで、リスト 3, 4 のプログラムはちょっとわかりにくいですね。この場合、関数を使うとわかりやすいプログラムを作ることができます。関数は次回で詳しく説明します。


初版 2011 年 4 月 16 日
改訂 2019 年 12 月 28 日