多次元配列 解答ページ | Programming Place Plus 新C++編

トップページ新C++編多次元配列

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (基本★)

生の二次元配列に、九九の結果を格納するプログラムを作成してください。


初期化時点で結果を入れてしまってもいいですが、一応きちんと計算しながら格納して、結果を出力するようにします。

#include <iomanip>
#include <iostream>

int main()
{
    int array[9][9] {};

    for (int i = 0; i < 9; ++i) {
        for (int j = 0; j < 9; ++j) {
            array[i][j] = (i + 1) * (j + 1);
        }
    }

    for (int i = 0; i < 9; ++i) {
        for (int j = 0; j < 9; ++j) {
            std::cout << std::setw(2) << array[i][j] << " ";
        }
        std::cout << "\n";
    }
}

実行結果:

 1  2  3  4  5  6  7  8  9
 2  4  6  8 10 12 14 16 18
 3  6  9 12 15 18 21 24 27
 4  8 12 16 20 24 28 32 36
 5 10 15 20 25 30 35 40 45
 6 12 18 24 30 36 42 48 54
 7 14 21 28 35 42 49 56 63
 8 16 24 32 40 48 56 64 72
 9 18 27 36 45 54 63 72 81

これといって難しいところはないと思います。

問題2 (基本★★)

std::vector を使って表現されたキャンバスの指定座標に、指定の色の点を描く関数を作成してください。

キャンバスの範囲外の座標を指定された場合には、単に無視するようにしてください。


std::vector を使ったキャンバスの表現や、色の表現は本編ページでも登場しているので、それに従うことにします(本編解説)。

#include <iomanip>
#include <iostream>
#include <vector>

struct Color {
    unsigned char red;          // 赤成分
    unsigned char green;        // 緑成分
    unsigned char blue;         // 青成分
};

// キャンバスの型
using canvas_t = std::vector<std::vector<Color>>;

// キャンバスの大きさを変更する
void resize_canvas(canvas_t& canvas, std::size_t width, std::size_t height)
{
    canvas.resize(height);
    for (auto& row : canvas) {
        row.resize(width);
    }
}

// キャンバス内の座標かどうか判定する
bool is_canvas_inside(const canvas_t& canvas, int x, int y)
{
    if (x < 0 || static_cast<int>(canvas[0].size()) <= x) {
        return false;
    }
    if (y < 0 || static_cast<int>(canvas.size()) <= y) {
        return false;
    }
    return true;
}

// キャンバスに点を描く
void paint_dot(canvas_t& canvas, int x, int y, Color color)
{
    if (!is_canvas_inside(canvas, x, y)) {
        return;
    }

    canvas.at(y).at(x) = color;
}

// キャンバスの内容を出力
void print_canvas(const canvas_t& canvas)
{
    for (const auto& row : canvas) {
        for (Color pixel : row) {
            std::cout << std::setw(6) << std::setfill('0') << std::hex << ((pixel.red << 16) | (pixel.green << 8) | (pixel.blue)) << " ";
        }
        std::cout << "\n";
    }
}

int main()
{
    canvas_t canvas {};
    resize_canvas(canvas, 10, 10);

    paint_dot(canvas, 0, 0, {255,   0,   0});
    paint_dot(canvas, 1, 1, {  0, 255,   0});
    paint_dot(canvas, 2, 0, {  0,   0, 255});

    print_canvas(canvas);
}

実行結果:

ff0000 000000 0000ff 000000 000000 000000 000000 000000 000000 000000
000000 00ff00 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000

現段階で大きなキャンバスを作る意味もないので、10x10 という小さなサイズにしました。resize_canvas関数は本編ページのままです(本編解説)。

キャンバスの大きさを表現するときに符号無し整数型を使うと、座標のほうも符号無し整数型にしようとするかもしれませんが、少なくとも座標のほうは、負数が指定できたほうが便利です。点を描く関数を組み合わせれば、直線や四角形などの図形を描くことも可能なわけですが、そうしたとき、画面外から画面内に向かって伸びる線を描くといったとき、画面外の座標が必要になります。つまり、キャンバスの範囲外の座標が指定されたときには、単に無視するのが正解です(範囲外をチェックすることが実行速度的に問題になるのなら検討が必要ですが)。

あとは、二次元配列の2つの添字のどちらが縦方向で、どちらが横方向なのかをよく意識してください。

問題3 (基本★★)

問題2の関数を利用して、キャンバスの指定の位置に、指定の大きさ・色の四角形を描くプログラムを作成してください。


問題2ができていれば簡単です。paint_rect関数を追加します。

#include <iomanip>
#include <iostream>
#include <vector>

struct Color {
    unsigned char red;          // 赤成分
    unsigned char green;        // 緑成分
    unsigned char blue;         // 青成分
};

// キャンバスの型
using canvas_t = std::vector<std::vector<Color>>;

// キャンバスの大きさを変更する
void resize_canvas(canvas_t& canvas, std::size_t width, std::size_t height)
{
    canvas.resize(height);
    for (auto& row : canvas) {
        row.resize(width);
    }
}

// キャンバス内の座標かどうか判定する
bool is_canvas_inside(const canvas_t& canvas, int x, int y)
{
    if (x < 0 || static_cast<int>(canvas[0].size()) <= x) {
        return false;
    }
    if (y < 0 || static_cast<int>(canvas.size()) <= y) {
        return false;
    }
    return true;
}

// キャンバスに点を描く
void paint_dot(canvas_t& canvas, int x, int y, Color color)
{
    if (!is_canvas_inside(canvas, x, y)) {
        return;
    }

    canvas.at(y).at(x) = color;
}

// キャンバスに四角形を描く
void paint_rect(canvas_t& canvas, int left, int top, int right, int bottom, Color color)
{
    for (int y {top}; y <= bottom; ++y) {
        for (int x {left}; x <= right; ++x) {
            paint_dot(canvas, x, y, color);
        }
    }
}

// キャンバスの内容を出力
void print_canvas(const canvas_t& canvas)
{
    for (const auto& row : canvas) {
        for (Color pixel : row) {
            std::cout << std::setw(6) << std::setfill('0') << std::hex << ((pixel.red << 16) | (pixel.green << 8) | (pixel.blue)) << " ";
        }
        std::cout << "\n";
    }
}

int main()
{
    canvas_t canvas {};
    resize_canvas(canvas, 10, 10);

    paint_rect(canvas, 5, 1, 12, 8, {255, 0, 0});

    print_canvas(canvas);
}

実行結果:

000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 ff0000 ff0000 ff0000 ff0000 ff0000
000000 000000 000000 000000 000000 000000 000000 000000 000000 000000

paint_dot関数が、キャンバスの範囲外の座標指定を単に無視していれば、四角形の一部が画面外に飛び出していても問題なく動作します。

問題4 (応用★★★)

5×5 の大きさの生の二次元配列に、1~75 から選ばれた重複のないランダムな数を、ランダムな位置に配置するプログラムを作成してください。

真ん中を特別あつかいしませんが、ビンゴゲームで配られるシートのイメージです。


たとえば次のように実装できます。

#include <iomanip>
#include <iostream>
#include <random>
#include <vector>

constexpr std::size_t sheet_width {5};      // シートの横方向のセル数
constexpr std::size_t sheet_height {5};     // シートの縦方向のセル数
constexpr int number_min {1};               // 使用される一番小さい数字
constexpr int number_max {75};              // 使用される一番大きい数字

int main()
{
    // シートを作る
    int sheet[sheet_height][sheet_width] {};
    
    // 数字のリストを作る
    std::vector<int> numbers {};
    for (int i = number_min; i <= number_max; ++i) {
        numbers.push_back(i);
    }
    
    // 数字のリストからランダムで1つを取り出して、シートの各マスにセットする
    std::random_device rand_dev {};
    std::mt19937 rand_engine(rand_dev());
    for (auto& row : sheet) {
        for (auto& cell : row) {

            std::uniform_int_distribution<int> dist(0, numbers.size() - 1);
            int index = dist(rand_engine);
            cell = numbers.at(index);

            // 使った数字はリストから取り除いて、2度と選ばれないようにする
            numbers.erase(std::begin(numbers) + index);
        }
    }

    // シートの内容を確認する
    for (const auto& row : sheet) {
        for (const auto& cell : row) {
            std::cout << std::setw(2) << cell << " ";
        }
        std::cout << "\n";
    }
}

実行結果:

53 30 47 28 65
73  8 34 27 13
66  1 20 15 46
35 19 39 50 67
 5 55 70 54 45

同じ数字が何度も選ばれないようにするためには、少々工夫が必要です。ここでは、先に 1~75 の数字が入ったリスト(numbers)を作っておき、ここからランダムに1つ取り出して使うようにしています。選ばれた数を numbers から取り除いておけば、同じ数字が登場してしまうことはありません。


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る