本演習では、C言語における多次元配列のメモリ配置、統計計算、ソート・中央値算出、行列変形、進数変換、および魔方陣判定といった実用的なプログラミング技法を体系的に扱います。各課題は独立した関数設計を前提とし、ポインタと配列の関係性、配列引数の受け渡し規則、および副作用を意識した実装が求められます。
一次元・二次元配列のメモリ構造解析
以下のコードは、int型配列のサイズと要素アドレスを出力することで、連続領域としての配列表現を可視化します。一時配列 values の全要素アドレスは等間隔(4バイト)で並び、values 自体は先頭要素のアドレスと同一です。二次元配列 matrix では、matrix[0] と matrix[1] の差分が N × sizeof(int)(例:4×4=16バイト)となる点に注目してください。
#include <stdio.h>
#define ROWS 2
#define COLS 4
void inspect_memory_layout() {
int values[COLS] = {1, 9, 8, 4};
printf("values 配列サイズ: %zu バイト\n", sizeof(values));
for (size_t idx = 0; idx < COLS; ++idx) {
printf("values[%zu] @ %p = %d\n", idx, (void*)&values[idx], values[idx]);
}
printf("配列名 values の値: %p\n", (void*)values);
int matrix[ROWS][COLS] = {{1, 9, 8, 4}, {2, 0, 4, 9}};
printf("\nmatrix 配列サイズ: %zu バイト\n", sizeof(matrix));
for (size_t r = 0; r < ROWS; ++r) {
for (size_t c = 0; c < COLS; ++c) {
printf("matrix[%zu][%zu] @ %p = %d\n", r, c, (void*)&matrix[r][c], matrix[r][c]);
}
}
printf("matrix = %p, matrix[0] = %p, matrix[1] = %p\n",
(void*)matrix, (void*)matrix[0], (void*)matrix[1]);
}
異常値除外型平均値計算
入力された整数列から最大値と最小値を除外し、残りの要素の算術平均を返す関数を実装します。配列長が2以下の場合のエラー処理は省略されていますが、実務ではこのケースに対する防御的チェックを追加すべきです。
double trimmed_mean(const int data[], size_t count) {
if (count < 3) return 0.0;
int max_val = data[0], min_val = data[0];
long sum = 0;
for (size_t i = 0; i < count; ++i) {
sum += data[i];
if (data[i] > max_val) max_val = data[i];
if (data[i] < min_val) min_val = data[i];
}
return (double)(sum - max_val - min_val) / (count - 2);
}
正方行列の初期化と表示
固定サイズの二次元配列を、指定されたスカラー値で初期化する関数と、整形された形式で出力する関数を分離して実装します。配列の第二添字サイズ(列数)はマクロ定義で固定され、関数シグネチャでは省略可能です。
#define MAX_SIZE 100
void fill_square_matrix(int grid[][MAX_SIZE], size_t dim, int fill_value) {
for (size_t row = 0; row < dim; ++row) {
for (size_t col = 0; col < dim; ++col) {
grid[row][col] = fill_value;
}
}
}
void print_square_matrix(const int grid[][MAX_SIZE], size_t dim) {
for (size_t row = 0; row < dim; ++row) {
for (size_t col = 0; col < dim; ++col) {
printf("%4d ", grid[row][col]);
}
putchar('\n');
}
}
中央値の算出と安定ソート
中央値を求めるには、まず昇順ソートが必要です。ここではバブルソートを採用し、奇数長なら中央要素、偶数長なら中央二要素の平均を返します。ソート関数は配列を破壊的に変更するため、必要に応じて元データの保存を検討してください。
void bubble_sort(int arr[], size_t len) {
for (size_t pass = 0; pass < len - 1; ++pass) {
for (size_t idx = 0; idx < len - 1 - pass; ++idx) {
if (arr[idx] > arr[idx + 1]) {
int tmp = arr[idx];
arr[idx] = arr[idx + 1];
arr[idx + 1] = tmp;
}
}
}
}
double calculate_median(int dataset[], size_t size) {
if (size == 0) return 0.0;
bubble_sort(dataset, size);
if (size % 2 == 1) {
return (double)dataset[size / 2];
} else {
size_t mid = size / 2;
return ((double)dataset[mid - 1] + dataset[mid]) / 2.0;
}
}
行列の右シフト変形
正方行列の各列を右方向に1つずらす操作を実行します。最右列の要素は左端列へ循環移動されます。この実装では、一時バッファを用いて列単位のデータ退避を行っています。
void shift_columns_right(int board[][MAX_SIZE], size_t order) {
if (order == 0) return;
int buffer[MAX_SIZE];
for (size_t r = 0; r < order; ++r) {
buffer[r] = board[r][order - 1];
}
for (size_t c = order - 1; c > 0; --c) {
for (size_t r = 0; r < order; ++r) {
board[r][c] = board[r][c - 1];
}
}
for (size_t r = 0; r < order; ++r) {
board[r][0] = buffer[r];
}
}
10進数から任意基数への変換
符号付き整数を指定基数(2/8/16)で文字列表現に変換する関数です。数字文字マップを静的に定義し、剰余計算で桁を逆順に生成した後、最終的に反転して出力します。
void convert_base(int number, int base) {
const char* symbols = "0123456789ABCDEF";
char buffer[MAX_SIZE];
size_t pos = 0;
int value = (number < 0) ? -number : number;
if (number == 0) {
printf("0\n");
return;
}
while (value != 0) {
buffer[pos++] = symbols[value % base];
value /= base;
}
if (number < 0) putchar('-');
while (pos-- > 0) {
putchar(buffer[pos]);
}
putchar('\n');
}
魔方陣の検証アルゴリズム
与えられた正方行列が魔方陣であるかを判定します。条件は以下の通りです:
・各行・各列・主対角線・副対角線の和が一致
・1〜n²のすべての整数が重複なく出現
内部では、出現済みフラグ配列を用いた重複検出と範囲チェックを実施しています。
int validate_magic_square(const int square[][MAX_SIZE], size_t n) {
if (n == 0) return 0;
// 基準和の取得(1行目)
int ref_sum = 0;
for (size_t c = 0; c < n; ++c) ref_sum += square[0][c];
// 行和チェック
for (size_t r = 1; r < n; ++r) {
int row_sum = 0;
for (size_t c = 0; c < n; ++c) row_sum += square[r][c];
if (row_sum != ref_sum) return 0;
}
// 列和チェック
for (size_t c = 0; c < n; ++c) {
int col_sum = 0;
for (size_t r = 0; r < n; ++r) col_sum += square[r][c];
if (col_sum != ref_sum) return 0;
}
// 対角線チェック
int diag1 = 0, diag2 = 0;
for (size_t i = 0; i < n; ++i) {
diag1 += square[i][i];
diag2 += square[i][n-1-i];
}
if (diag1 != ref_sum || diag2 != ref_sum) return 0;
// 数字の重複・範囲チェック
int seen[n * n + 1] = {0};
for (size_t r = 0; r < n; ++r) {
for (size_t c = 0; c < n; ++c) {
int val = square[r][c];
if (val < 1 || val > n * n || seen[val]) return 0;
seen[val] = 1;
}
}
return 1;
}