ざっくりZig - 繰り返しの応用 (for, while, else, break, continue, オプション, ラベル)

2024/04/01に公開

forによる繰り返し

繰り返しにはwhileのほかにforもあります。これはwhileによる繰り返し処理をもう少しシンプルに書けるようにしたものです。

以下は変数iの値が0~4のときi = 0i = 4まで出力する処理を表します。

whileとforによる繰り返し
// while
var i: u8 = 0;
while (i < 5) : (i += 1) {      // 繰り返し処理
    try stdout.print("i = {}\n", .{i});
}

// for
for (0..5) |i| {     // 繰り返し処理
    try stdout.print("i = {}\n", .{i});
}

// 結果
i = 0
i = 1
i = 2
i = 3
i = 4

whileの場合、変数iは繰り返しの外で定義し、i += 1のように変数の値を更新する処理も明示しています。一方forの場合は変数を1|i|のように繰り返しの中で定義し、変数の値を更新する処理は明示していません。

また、変数が取り得る値を(0..5)のように範囲で書いていますが、..の左辺を右辺より必ず小さい値とします。これにより、iの値が0から4まで1ずつ増加し、5にはならないのがポイントです。

forで複数の変数を扱うときは、(0..5, 5..10)のようにカンマ区切りで複数の範囲を書き、変数も|i, j|のように書きます。こうするとi0..5j5..10の範囲で変化します。変数がとりうる値の数はすべての変数で同じになるようにします。これらの変数は繰り返しの中で同時に変化していきます。

複数の変数を使うfor
// for
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    for (0..5, 5..10) |i, j| {
        try stdout.print("i = {}, j = {}\n", .{i, j});
    }
}

// 結果
i = 0, j = 5
i = 1, j = 6
i = 2, j = 7
i = 3, j = 8
i = 4, j = 9

なお、|i|などの変数を使用しないときは|_|のようにします。

forで変数を使用しないとき
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    for (0..5) |_| { // 変数を使用しない
        try stdout.print("forによる繰り返し\n", .{});
    }
}

代入とelse

whileforのどちらも末尾のelse ...もしくはelse { ... }で繰り返しの終了後に行う処理を定義できます。これは代入と組み合わせて繰り返し処理後の判定結果などを設定するのに利用できます。

whileとforの末尾にelseを追加(代入)
// while .. else
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    for (2..10) |n| {
        var i: u8 = 2;
        // while..elseでnが素数かを判定
        const isPrime = while (i < n): (i += 1) {
            if (n % i == 0) {
                break false;    // nは素数じゃない
            }
        } else true;    // nは素数
        try stdout.print("isPrime {} = {}\n", .{n, isPrime});
    }
}

// for .. else
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    for (2..10) |n| {
        // for..elseでnが素数かを判定
        const isPrime = for (2..n) |i| {
            if (n % i == 0) {
                break false;    // nは素数じゃない
            }
        } else true;    // nは素数
        try stdout.print("isPrime {} = {}\n", .{n, isPrime});
    }
}

// 結果
isPrime 2 = true
isPrime 3 = true
isPrime 4 = false
isPrime 5 = true
isPrime 6 = false
isPrime 7 = true
isPrime 8 = false
isPrime 9 = false

whileで変化させる変数がオプション型のとき

whileで変化させる変数が?u8のようなオプション型の時はwhile (オプション型の変数) |繰り返し処理で使用する変数| { ... }のようにすると、(...)の変数の値がnullになるまで繰り返し処理を行います。

変数がオプション型のとき
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var i: ?u8 = 0;
    while (i) |c| : ({
            i.? += 1;
            if (i.? > 4) { i = null; }
    }) {
        try stdout.print("i = {}, c = {}\n", .{i.?, c});
    }
    try stdout.print("i = {?}\n", .{i});
}

// 結果
c = 0, i = 0
c = 1, i = 1
c = 2, i = 2
c = 3, i = 3
c = 4, i = 4
c = null

breakとcontinueによるラベル指定

breakcontinueでは行き先をラベルで指定できます。これにより、多重の繰り返し処理になっているとき、行き先を外側の繰り返しの方に指定できます。ラベルをつけるときは行の最初にラベル名:、行先を指定するときは:ラベル名とします。

breakとcontinueでのラベル指定
const std = @import("std");
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    outer: for (1..5) |i| {     // 先頭にラベル
        for (1..5) |j| {
            if (j > 1) {
                try stdout.print("\t", .{});
            }
            if (i * j > 5) {
                try stdout.print("\n", .{});
                continue :outer;    // 行き先をラベルで指定
            }
            try stdout.print("{} * {} = {d:2}", .{i, j, i * j});
        }
        try stdout.print("\n", .{});
    }
}

// 結果
1 * 1 =  1	1 * 2 =  2	1 * 3 =  3	1 * 4 =  4
2 * 1 =  2	2 * 2 =  4	
3 * 1 =  3	
4 * 1 =  4

// continueをbreakに変更したとき
1 * 1 =  1	1 * 2 =  2	1 * 3 =  3	1 * 4 =  4
2 * 1 =  2	2 * 2 =  4

まとめ

  • forwhileと違い、変数を|i|のように繰り返しの中で定義し、変数がとりうる値も(0..5)とする
    このとき変数の値は必ず1ずつ増加し、iの値は5にならない
  • forで複数の変数を|i, j|のように定義でき、変化の範囲は(0..5, 5..10)のように書く
    これらの変数は同時に変化する
  • forで変数を使用しないときは|_|とする
  • whileforのいずれも末尾にelseで繰り返し終了後の処理を追加し、代入の時に繰り返し後の判定処理などで利用できる
  • whileでは変数がオプション型の時、値がnullになるまで繰り返し処理を行う
  • breakcontinueでは行き先をラベルで指定できる

配列とタプル >
< 繰り返し (while, break, continue)
ざっくりZig 一覧

Discussion