0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

paiza問題集「先頭の要素の削除」の入力チェック・テストコードを考えてみた。

Posted at

paizaラーニングレベルアップ問題集の先頭の要素の削除(query)をやってみました。


問題


絶対アカンコード

popが来るたびに最大10万要素が“民族大移動”になります。

#include <stdio.h>
#include <string.h>

int main() {
	int N, K;
	scanf("%d %d", &N, &K);
	int A[N];
	for (int i = 0; i < N; i++)
		scanf("%d", &A[i]);
	while (K--) {
		char s[5];
		scanf("%s", s);
		if (!strcmp(s, "pop")) {
			if (N) memmove(A, A + 1, sizeof(A[0]) * --N);
		} else if (!strcmp(s, "show")) {
			for (int i = 0; i < N; i++)
				printf("%d\n", A[i]);
		}
	}
	return 0;
}

とりあえずリファクタリング

一旦、この「絶対アカンコード」を元に、関数分割したいと思います。
C言語にクラスはありませんが、オブジェクト指向で

  • 配列はコンストラクタの引数に
  • クエリはメソッドの引数(今回はメソッド名そのもの)に

渡したいと思います。


UShortArray.h
#ifndef USHORTARRAY_H_
#define USHORTARRAY_H_

#include <stdbool.h>
#include <inttypes.h>

typedef struct __UShortArray _UShortArray;

typedef struct UShortArray {
	_UShortArray *_uShortArray;
	bool (*pop)(struct UShortArray*);
	bool (*show)(const struct UShortArray*);
} UShortArray;

UShortArray* new_UShortArray(size_t, const uint16_t*);

void free_UShortArray(UShortArray**);

#endif /* USHORTARRAY_H_ */

UShortArray.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "UShortArray.h"
#define LIMIT (10)

struct __UShortArray {
	uint8_t num;
	size_t n;
	uint16_t A[];
};

bool UShortArray_pop(UShortArray *uShortArray) {
	if (uShortArray->_uShortArray->n == 0) {
		return false;
	}
	size_t *n = &(uShortArray->_uShortArray->n);
	uint16_t *A = uShortArray->_uShortArray->A;
	memmove(A, A + 1, sizeof(A[0]) * --*n);
	_UShortArray *temp = (_UShortArray*) realloc(
    	uShortArray->_uShortArray,
		sizeof(_UShortArray) + sizeof(A[0]) * *n
    );
	if (!temp) {
		free(uShortArray->_uShortArray);
		uShortArray->_uShortArray = NULL;
		return false;
	}
	uShortArray->_uShortArray = temp;
	return true;
}

bool UShortArray_show(const UShortArray *uShortArray) {
	if (uShortArray->_uShortArray->num >= LIMIT) {
		return false;
	}
	for (size_t i = 0; i < uShortArray->_uShortArray->n; i++) {
		printf("%"PRIu16"\n", uShortArray->_uShortArray->A[i]);
	}
	uShortArray->_uShortArray->num += 1;
	return true;
}

UShortArray* new_UShortArray(size_t n, const uint16_t *A) {
	UShortArray* uShortArray = (UShortArray*) malloc(sizeof(UShortArray));
	if (!uShortArray) {
		return NULL;
	}
	uShortArray->_uShortArray = (_UShortArray*) malloc(
		sizeof(_UShortArray) + sizeof(uShortArray->_uShortArray->A[0]) * n
    );
	if (uShortArray->_uShortArray == NULL) {
		free(uShortArray);
		return NULL;
	}
	uShortArray->_uShortArray->num = 0;
	uShortArray->_uShortArray->n = n;
	memcpy(
		uShortArray->_uShortArray->A,
		A,
		sizeof(uShortArray->_uShortArray->A[0]) * n
	);
	uShortArray->pop = UShortArray_pop;
	uShortArray->show = UShortArray_show;
	return uShortArray;
}

void free_UShortArray(UShortArray** uShortArray) {
	free((*uShortArray)->_uShortArray);
	(*uShortArray)->_uShortArray = NULL;
	free(*uShortArray);
	*uShortArray = NULL;
}

時間計測

一旦、時間計測してみます。

main.c
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "UShortArray.h"

int main() {
	clock_t clockt = clock();
	size_t N = 100000;
	size_t K = 100000;
	uint16_t A[N];
	for (size_t i = 0; i < N; i++) {
		A[i] = i % 10001;
	}
	UShortArray *uShortArray = new_UShortArray(N, A);
	while (K--) {
        char *s = K % 10000 ? "pop" : "show";
        if (!strcmp(s, "pop")) {
            uShortArray->pop(uShortArray);
        } else if (!strcmp(s, "show")) {
            uShortArray->show(uShortArray);
        }
	}
	free_UShortArray(&uShortArray);
	fprintf(stderr, "%f sec", (double) (clock() - clockt) / CLOCKS_PER_SEC);
	return 0;
}

Windows環境にて、以下のバッチを作成して実行しました。

main.bat
echo off
gcc -Wall -Wextra -Werror -std=c99 main.c UShortArray.c -o main 1>main.txt 2>&1
if exist main.exe (
	for /l %%i in (0,1,9) do (
		.\main 1>nul
	)
	del main.exe
	del main.txt
) else (
	type main.txt
)
pause

実行結果:

3.101000 sec
2.815000 sec
2.877000 sec
2.891000 sec
2.911000 sec
3.050000 sec
3.010000 sec
2.858000 sec
2.917000 sec
2.821000 sec
続行するには何かキーを押してください . . .

3秒前後でした。


計算量について意識

実は、memmove関数で“民族大移動”をする必要はありません。配列の開始位置を管理しておけばよいです。
そこで、UShortArray.cを以下のように修正します。構造体を構成する変数名も変更しておきます。後のテストの参考として、構造体を構成する各要素のアドレスもデバッグコードで出しております。

UShortArray.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "UShortArray.h"
#define LIMIT (10)

struct __UShortArray {
	uint8_t times;
	size_t size;
	size_t front;
	uint16_t array[];
};

bool UShortArray_pop(UShortArray *uShortArray) {
	size_t n = uShortArray->_uShortArray->size;
	size_t *m = &(uShortArray->_uShortArray->front);
	if (*m >= n) {
		return false;
	}
	*m += 1;
	return true;
}

bool UShortArray_show(const UShortArray *uShortArray) {
	if (uShortArray->_uShortArray->times >= LIMIT) {
		return false;
	}

	size_t n = uShortArray->_uShortArray->size;
	size_t m = uShortArray->_uShortArray->front;
	uint16_t *A = uShortArray->_uShortArray->array;
	for (size_t i = m; i < n; i++) {
		printf("%"PRIu16"\n", A[i]);
	}
	fflush(stdout);
	uShortArray->_uShortArray->times += 1;
	return true;
}

UShortArray* new_UShortArray(size_t n, const uint16_t *A) {
	UShortArray* uShortArray = (UShortArray*) malloc(sizeof(UShortArray));
	if (!uShortArray) {
		return NULL;
	}
	uShortArray->_uShortArray = (_UShortArray*) malloc(
		sizeof(_UShortArray) + sizeof(uShortArray->_uShortArray->array[0]) * n
    );
	if (uShortArray->_uShortArray == NULL) {
		free(uShortArray);
		return NULL;
	}
#ifndef NDEBUG
	fprintf(stderr,
		"&times: %p\n"
		"&size : %p\n"
		"&front: %p\n"
		"&array: %p\n",
		&(uShortArray->_uShortArray->times),
		&(uShortArray->_uShortArray->size),
		&(uShortArray->_uShortArray->front),
		uShortArray->_uShortArray->array
	);
#endif
	uShortArray->_uShortArray->times = 0;
	uShortArray->_uShortArray->size = n;
	uShortArray->_uShortArray->front = 0;
	memcpy(
		uShortArray->_uShortArray->array,
		A,
		sizeof(uShortArray->_uShortArray->array[0]) * n
	);
	uShortArray->pop = UShortArray_pop;
	uShortArray->show = UShortArray_show;
	return uShortArray;
}

void free_UShortArray(UShortArray** uShortArray) {
	free((*uShortArray)->_uShortArray);
	(*uShortArray)->_uShortArray = NULL;
	free(*uShortArray);
	*uShortArray = NULL;
}

実行結果:

2.748000 sec
2.574000 sec
2.676000 sec
2.753000 sec
2.695000 sec
2.678000 sec
2.634000 sec
2.635000 sec
2.604000 sec
2.647000 sec
続行するには何かキーを押してください . . .

2.7秒前後になりました(“諸”々の“諸”事情によりあまり変わらないが)。


テストコード

ここで、UShortArrayクラス(C言語には“クラス”はないが)の単体テストコードを書きたいと思います。
UShortArrayクラスでは

  • popメソッド
    • popの回数が要素数を超えた場合失敗
  • showメソッド
    • showの回数が10回を超えた場合失敗

を中心にテストします。
popの回数が要素数を超える」状況、「showの回数が10回を超える」状況は、未定義構造体struct __UShortArrayへのポインタを使ってアクセスすることにより、値を書き換えることもできます(たぶん非推奨)。
コンストラクタ、デストラクタの試験もあります。これも未定義構造体struct __UShortArrayへのポインタを駆使して以下の様にも書けますが、他のメソッドの試験に混ぜて実施すればよいでしょう。

そもそも、構造体struct __UShortArrayの最初の変数timesのメモリサイズが、sizeof(uint8_t)バイトと指定するのは誤りだと判明したのですが、sizeof(void*)なのか、sizeof(size_t)なのか、直値で8なのか、テキトーにやっています。


尚、単体テストツールとしてminunit.hを使うことにします。


UShortArrayTest.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "UShortArray.h"
#include "minunit.h"

int tests_run = 0;

static char* message_s(const char *expected, const char *actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%s> but was: <%s>", expected, actual);
	return msg;
}

static char* message_u8(uint8_t expected, uint8_t actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%"PRIu8"> but was: <%"PRIu8">", expected, actual);
	return msg;
}

static char* message_u16(uint16_t expected, uint16_t actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%"PRIu16"> but was: <%"PRIu16">", expected, actual);
	return msg;
}

static char* message_z(size_t expected, size_t actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%zu> but was: <%zu>", expected, actual);
	return msg;
}

static void set_values(_UShortArray *uShortArray, uint8_t times, size_t front) {
	void *p = (void*) uShortArray;
	uint8_t *pTimes = (uint8_t*) p;
	*pTimes = times;
	// size_t *pFront = (size_t*) (((void*) pTimes) + sizeof(uint8_t) + sizeof(size_t));
	size_t *pFront = (size_t*) (((void*) pTimes) + sizeof(void*) + sizeof(size_t));
	*pFront = front;
}

static char* test_constructor(size_t n, uint16_t *A) {
	UShortArray *uShortArray = new_UShortArray(n, A);
	void *p = (void*) uShortArray->_uShortArray;
	uint8_t times = *(uint8_t*) p;
	p += sizeof(void*);//sizeof(times);
	size_t size = *(size_t*) p;
	p += sizeof(size);
	size_t front = *(size_t*) p;
	p += sizeof(front);
	uint16_t actual[n];
	memcpy(actual, p, sizeof(actual));
	free_UShortArray(&uShortArray);
	mu_assert(message_u8(0, times), times == 0);
	mu_assert(message_z(n, size), size == n);
	mu_assert(message_z(0, front), front == 0);
	for (size_t i = 0; i < n; i++) {
		mu_assert(message_u16(A[i], actual[i]), actual[i] == A[i]);
	}
	mu_assert("Error: expected: <null> but was: <not null>", !uShortArray);
	return 0;
}

static char* test_constructor_0() {
	return test_constructor(0, NULL);
}

static char* test_constructor_1() {
	uint16_t A[] = { };
	return test_constructor(0, A);
}

static char* test_constructor_2() {
	uint16_t A[] = { 0 };
	return test_constructor(1, A);
}

static char* test_constructor_3() {
	uint16_t A[100000];
	for (size_t i = 0; i < 100000; i++) {
		A[i] = i % 10000 + 1;
	}
	return test_constructor(100000, A);
}

static char* test_pop_success(size_t n, uint16_t *A, size_t front) {
	UShortArray *uShortArray = new_UShortArray(n, A);
	set_values(uShortArray->_uShortArray, 0, front);
	bool result = uShortArray->pop(uShortArray);
	// size_t actual = *(size_t*) ((void*) (uShortArray->_uShortArray) + sizeof(uint8_t) + sizeof(size_t));
	size_t actual = *(size_t*) ((void*) (uShortArray->_uShortArray) + sizeof(void*) + sizeof(size_t));
	free_UShortArray(&uShortArray);
	mu_assert("Error: expected: <true> but was: <false>", result);
	mu_assert(message_z(-~front, actual), actual == -~front);
	return 0;
}

static char* test_pop_failure(size_t n, uint16_t *A, size_t front) {
	UShortArray *uShortArray = new_UShortArray(n, A);
	set_values(uShortArray->_uShortArray, 0, front);
	bool result = uShortArray->pop(uShortArray);
	// size_t actual = *(size_t*) ((void*) (uShortArray->_uShortArray) + sizeof(uint8_t) + sizeof(size_t));
	size_t actual = *(size_t*) ((void*) (uShortArray->_uShortArray) + sizeof(void*) + sizeof(size_t));
	free_UShortArray(&uShortArray);
	mu_assert("Error: expected: <false> but was: <true>", !result);
	mu_assert(message_z(front, actual), actual == front);
	return 0;
}

static char* test_pop_0() {
	return test_pop_failure(0, NULL, 0);
}

static char* test_pop_1() {
	uint16_t A[] = { 0 };
	return test_pop_success(1, A, 0);
}

static char* test_pop_2() {
	uint16_t A[] = { 0 };
	return test_pop_failure(1, A, 1);
}

static char* test_show_success(size_t n, uint16_t *A, size_t front, size_t m, char *expected[], uint8_t times) {
	UShortArray *uShortArray = new_UShortArray(n, A);
	set_values(uShortArray->_uShortArray, times, front);
	const char* filename = "out.txt";
	FILE *fp = freopen(filename, "w", stdout);
	if (!fp) {
		free_UShortArray(&uShortArray);
		return strerror(errno);
	}	
	bool result = uShortArray->show(uShortArray);
	uint8_t t = *(uint8_t*) ((void*) uShortArray->_uShortArray);
	free_UShortArray(&uShortArray);
#if defined(_WIN32) || defined(_WIN64)
	fp = freopen("con", "w", stdout);
#else
	fp = freopen("/dev/tty", "w", stdout);
#endif
	if (!fp) {
		return strerror(errno);
	}
	fp = fopen(filename, "r");
	if (!fp) {
		return strerror(errno);
	}
	char actual[m+1][7];
	for (size_t i = 0; i < m; i++) {
		fgets(actual[i], sizeof(actual[i]), fp);
	}
	char *null = fgets(actual[m], sizeof(actual[m]), fp);
	fclose(fp);
	remove(filename);
	mu_assert("Error: expected: <true> but was: <false>", result);
	mu_assert(message_z(-~times, t), t == -~times);
	for (size_t i = 0; i < m; i++) {
		mu_assert(message_s(expected[i], actual[i]), !strcmp(actual[i], expected[i]));
	}
	mu_assert("Error: expected: <false> but was: <true>", !null);
	return 0;
}

static char* test_show_failure(size_t n, uint16_t *A, size_t front, uint8_t times) {
	UShortArray *uShortArray = new_UShortArray(n, A);
	set_values(uShortArray->_uShortArray, times, front);
	const char* filename = "out.txt";
	FILE *fp = freopen(filename, "w", stdout);
	if (!fp) {
		free_UShortArray(&uShortArray);
		return strerror(errno);
	}	
	bool result = uShortArray->show(uShortArray);
	uint8_t t = *(uint8_t*) ((void*) uShortArray->_uShortArray);
	free_UShortArray(&uShortArray);
#if defined(_WIN32) || defined(_WIN64)
	fp = freopen("con", "w", stdout);
#else
	fp = freopen("/dev/tty", "w", stdout);
#endif
	if (!fp) {
		return strerror(errno);
	}
	fp = fopen(filename, "r");
	if (!fp) {
		return strerror(errno);
	}
	char actual[2];
	char *null = fgets(actual, sizeof(actual), fp);
	fclose(fp);
	remove(filename);
	mu_assert("Error: expected: <false> but was: <true>", !result);
	mu_assert(message_z(times, t), t == times);
	mu_assert("Error: expected: <false> but was: <true>", !null);
	return 0;
}

static char* test_show_0() {
	return test_show_failure(0, NULL, 0, 10);
}

static char* test_show_1() {
	return test_show_success(0, NULL, 0, 0, NULL, 9);
}

static char* test_show_2() {
	uint16_t A[] = { 0 };
	char *B[] = { "0\n" };
	return test_show_success(1, A, 0, 1, B, 0);
}

static char* test_show_3() {
	uint16_t A[] = { 1, 2 };
	char *B[] = { "1\n", "2\n" };
	return test_show_success(2, A, 0, 2, B, 0);
}

static char* test_show_4() {
	uint16_t A[] = { 1, 2 };
	char *B[] = { "2\n" };
	return test_show_success(2, A, 1, 1, B, 0);
}

static char* test_show_5() {
	uint16_t A[] = { 1, 2 };
	char *B[] = { NULL };
	return test_show_success(2, A, 2, 0, B, 0);
}

static char* all_tests() {
	mu_run_test(test_constructor_0);
	mu_run_test(test_constructor_1);
	mu_run_test(test_constructor_2);
	mu_run_test(test_constructor_3);
	mu_run_test(test_pop_0);
	mu_run_test(test_pop_1);
	mu_run_test(test_pop_2);
	mu_run_test(test_show_0);
	mu_run_test(test_show_1);
	mu_run_test(test_show_2);
	mu_run_test(test_show_3);
	mu_run_test(test_show_4);
	mu_run_test(test_show_5);
	return 0;
}

int main() {
	char *result = all_tests();
	if (result != 0) {
		fprintf(stderr, "%s\n", result);
		fprintf(stderr, "Tests run: %d\n", tests_run);
	} else {
		fprintf(stdout, "ALL TESTS PASSED\n");
		fprintf(stdout, "Tests run: %d\n", tests_run);
	}
	return result != 0;
}

Windows環境の場合、以下のバッチファイルを作っておけばテストしやすいでしょう。

UShortArrayTest.bat
echo off
gcc -Wall -Wextra -Werror -std=c99 UShortArrayTest.c UShortArray.c -o UShortArrayTest 1>UShortArrayTest.txt 2>&1
if exist UShortArrayTest.exe (
	.\UShortArrayTest
	del UShortArrayTest.exe
	del UShortArrayTest.txt
) else (
	type UShortArrayTest.txt
)
pause

入力チェック

問題集や競技プログラミングでは制約条件どおりの入力が与えられますが、実際はそうとは限りません。
そこで、意図せぬ入力値が来ても適切に処理できるよう、入力チェックをするようにします。
今回は、

  • $1\le K\le N\le 100000$
  • $0\le A_i\le 10000$($1\le i\le N$)
  • $S_i$($1\le i\le K$)はpop, showのいずれか
  • $S_i$のうち、showであるものは10個以下

ですので、最初の2個の

  • 整数チェック
  • 範囲チェック

を実装します。


input.h
#ifndef INPUT_H_
#define INPUT_H_

#include <stdbool.h>
#include <inttypes.h>

char* chomp(char*);

bool read_size(const char*, size_t*, size_t*);

bool read_element(const char*, uint16_t*);

#endif /* INPUT_H_ */

input.c
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "input.h"

char* chomp(char *str) {
	size_t n = strlen(str);
	while (n--) {
		if (isspace(str[n])) {
			str[n] = '\0';
		} else {
			break;
		}
	}
	return str;
}

static bool is_number(const char *str) {
	if (!(str && *str)) {
		return false;
	}
	if (str[0] == '0') {
		return strlen(str) == 1;
	}
	for (const char *c = str; *c; c++) {
		if (!isdigit(*c)) {
			return false;
		}
	}
	return true;
}

static bool parse(const char *str, uintmax_t *u, uintmax_t max) {
	errno = 0;
	if (!is_number(str)) {
		errno = EINVAL;
		return false;
	}
	char *s = NULL;
	*u = strtoumax(str, &s, 10);
	if (errno) {
		return false;
	} else if (s && *s) {
		errno = EINVAL;
		return false;
	} else if (*u > max) {
		errno = ERANGE;
		return false;
	}
	return true;
}


static bool parse_ulong(const char *str, size_t *n) {
	errno = 0;
	size_t m;
	if (!parse(str, &m, SIZE_MAX)) {
		return false;
	}
	*n = (size_t) m;
	return true;
}

static bool parse_ushort(const char *str, uint16_t *n) {
	errno = 0;
	uintmax_t m;
	if (!parse(str, &m, UINT16_MAX)) {
		return false;
	}
	*n = (uint16_t) m;
	return true;
}

bool read_size(const char *str, size_t *n, size_t *k) {
	errno = 0;
	if (strlen(str) > 14) {
		errno = EINVAL;
		return false;
	}
	char s[15] = "";
	char *t = strtok(chomp(strncpy(s, str, sizeof(s))), " ");
	if (!parse_ulong(t, n)) {
		return false;
	} else if (*n < 1 || 100000 < *n) {
		errno = ERANGE;
		return false;
	}
	t = strtok(NULL, " ");
	if (!parse_ulong(t, k)) {
		return false;
	} else if (*k < 1 || *n < *k) {
		errno = ERANGE;
		return false;
	}
	t = strtok(NULL, " ");
	if (t) {
		errno = EINVAL;
		return false;
	}
	return true;
}

bool read_element(const char* str, uint16_t* a) {
	errno = 0;
	if (strlen(str) > 6) {
		errno = EINVAL;
		return false;
	}
	char s[7] = "";
	chomp(strncpy(s, str, sizeof(s)));
	if (!parse_ushort(s, a)) {
		return false;
	} else if (*a > 10000) {
		errno = ERANGE;
		return false;
	}
	return true;
}

テストコード

入力値についても、整数値の入ってくる箇所は

  • 数字のみか
  • 範囲内か

をチェックします。

inputTest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "minunit.h"
#include "input.h"

int tests_run = 0;

static char* message_d(int expected, int actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%d> but was: <%d>", expected, actual);
	return msg;
}

static char* message_u(uint16_t expected, uint16_t actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%"PRIu16"> but was: <%"PRIu16">", expected, actual);
	return msg;
}

static char* message_z(size_t expected, size_t actual) {
	static char msg[80];
	snprintf(msg, sizeof(msg), "Error: expected: <%zu> but was: <%zu>", expected, actual);
	return msg;
}

static char* test_read_size(const char *str, size_t en, size_t ek) {
	size_t n, k;
	mu_assert("Error: expected: <true> but was: <false>", read_size(str, &n, &k));
	mu_assert(message_d(errno, 0), errno == 0);
	mu_assert(message_z(en, n), n == en);
	mu_assert(message_z(ek, k), k == ek);
	return 0;
}

static char* test_read_size_invalid(const char *str) {
	size_t n, q;
	mu_assert("Error: expected: <false> but was: <true>", !read_size(str, &n, &q));
	mu_assert(message_d(EINVAL, errno), errno == EINVAL);
	return 0;
}

static char* test_read_size_out_of_range(const char *str) {
	size_t n, q;
	mu_assert("Error: expected: <false> but was: <true>", !read_size(str, &n, &q));
	mu_assert(message_d(ERANGE, errno), errno == ERANGE);
	return 0;
}

static char* test_read_size_0() {return test_read_size_invalid(NULL);}
static char* test_read_size_1() {return test_read_size_invalid("");}
static char* test_read_size_2() {return test_read_size_invalid("1");}
static char* test_read_size_3() {return test_read_size_out_of_range("0 1");}
static char* test_read_size_4() {return test_read_size_out_of_range("1 0");}
static char* test_read_size_5() {return test_read_size("1 1", 1, 1);}
static char* test_read_size_6() {return test_read_size("100000 100000", 100000, 100000);}
static char* test_read_size_7() {return test_read_size_out_of_range("1 2");}
static char* test_read_size_8() {return test_read_size_out_of_range("100001 100000");}
static char* test_read_size_9() {return test_read_size_out_of_range("100000 100001");}
static char* test_read_size_10() {return test_read_size_invalid("10000 10000 1");}
static char* test_read_size_11() {return test_read_size_invalid("100000 100000 1");}

static char* test_read_element(const char *str, uint16_t expected) {
	uint16_t actual;
	mu_assert("Error: expected: <true> but was: <false>", read_element(str, &actual));
	mu_assert(message_d(0, errno), errno == 0);
	mu_assert(message_u(expected, actual), actual == expected);
	return 0;
}

static char* test_read_element_invalid(const char *str) {
	uint16_t actual;
	mu_assert("Error: expected: <false> but was: <true>", !read_element(str, &actual));
	mu_assert(message_d(EINVAL, errno), errno == EINVAL);
	return 0;
}

static char* test_read_element_out_of_range(const char *str) {
	uint16_t actual;
	mu_assert("Error: expected: <false> but was: <true>", !read_element(str, &actual));
	mu_assert(message_d(ERANGE, errno), errno == ERANGE);
	return 0;
}

static char* test_read_element_0() {return test_read_element_invalid(NULL);}
static char* test_read_element_1() {return test_read_element_invalid("");}
static char* test_read_element_2() {return test_read_element_invalid("-1");}
static char* test_read_element_3() {return test_read_element("0", 0);}
static char* test_read_element_4() {return test_read_element("10000", 10000);}
static char* test_read_element_5() {return test_read_element_out_of_range("10001");}

static char* all_tests() {
	mu_run_test(test_read_size_0);
	mu_run_test(test_read_size_1);
	mu_run_test(test_read_size_2);
	mu_run_test(test_read_size_3);
	mu_run_test(test_read_size_4);
	mu_run_test(test_read_size_5);
	mu_run_test(test_read_size_6);
	mu_run_test(test_read_size_7);
	mu_run_test(test_read_size_8);
	mu_run_test(test_read_size_9);
	mu_run_test(test_read_size_10);
	mu_run_test(test_read_size_11);
	mu_run_test(test_read_element_0);
	mu_run_test(test_read_element_1);
	mu_run_test(test_read_element_2);
	mu_run_test(test_read_element_3);
	mu_run_test(test_read_element_4);
	mu_run_test(test_read_element_5);
	return 0;
}

int main() {
	char *result = all_tests();
	if (result != 0) {
		fprintf(stderr, "%s\n", result);
		fprintf(stderr, "Tests run: %d\n", tests_run);
	} else {
		fprintf(stdout, "ALL TESTS PASSED\n");
		fprintf(stdout, "Tests run: %d\n", tests_run);
	}
	return result != 0;
}

inputTest.bat
echo off
gcc -Wall -Wextra -Werror -std=c99 inputTest.c input.c -o inputTest 1>inputTest.txt 2>&1
if exist inputTest.exe (
	.\inputTest
	echo %ERRORLEVEL%
	del inputTest.exe
	del inputTest.txt
) else (
	type inputTest.txt
)
pause

エントリポイント

これらのモジュールを使って、mainを書き換えます。但し、ここで後半のクエリ(文字列)のチェックを忘れないようにします。既に、showは10個以下、というのはUShortArrayshowメソッドに既に仕込んであります。尚、不正な入力があった場合は再入力を促します。
以下のコードでは、デバッグコードして速度計測版を残してあります。


main.c
// #define NDEBUG
#include <stdio.h>
#include <string.h>
#include "UShortArray.h"
#ifdef NDEBUG
#include "input.h"
#else
#include <time.h>
#endif

int main() {
#ifndef NDEBUG
	clock_t clockt = clock();
#endif
	size_t N, K;
#ifdef NDEBUG
	for (;;) {
		char s[15];
		fgets(s, sizeof(s), stdin);
		if (!strchr(s, '\n')) {
			for (int c = getchar(); !(c == '\0' || c == '\n' || c == EOF); c = getchar());
		} else if (read_size(s, &N, &K)) {
			break;
		}
		perror("The number of elements must be an integer between 1 and 100000.\n"
				"The number of queries must be an integer between 1 and the number of elements.");
	}
#else
	N = 100000;
	K = 100000;
#endif
	uint16_t A[N];
	for (size_t i = 0; i < N; i++) {
#ifdef NDEBUG
		for (;;) {
			char s[7];
			fgets(s, sizeof(s), stdin);
			if (!strchr(s, '\n')) {
				for (int c = getchar(); !(c == '\0' || c == '\n' || c == EOF); c = getchar());
			} else if (read_element(s, &A[i])) {
				break;
			}
			perror("The element must be an integer between 0 and 1000000.");
		}
#else
		A[i] = i % 10001;
#endif
	}
	UShortArray *uShortArray = new_UShortArray(N, A);
	while (K--) {
		for (;;) {
			char s[6] = "";
#ifdef NDEBUG
			fgets(s, sizeof(s), stdin);
			if (!strchr(s, '\n')) {
				for (int c = getchar(); !(c == '\0' || c == '\n' || c == EOF); c = getchar());
				fprintf(stderr, "Input \"pop\" or \"show\".");
				fflush(stderr);
				continue;
			}
			chomp(s);
#else
			strncpy(s, K % 10000 ? "pop" : "show", sizeof(s));
#endif
			if (!strcmp(s, "pop")) {
				uShortArray->pop(uShortArray);
				break;
			} else if (!strcmp(s, "show")) {
				if (uShortArray->show(uShortArray)) {
					break;
				}
			}
			fprintf(stderr, "Input \"pop\" or \"show\".");
			fflush(stderr);
		}
	}
	free_UShortArray(&uShortArray);
#ifndef NDEBUG
	fprintf(stderr, "%f sec\n", (double) (clock() - clockt) / CLOCKS_PER_SEC);
#endif
	return 0;
}

テストコード

system関数を使って、単体テストと同じようなテストコードを書くこともできます(実際には、system関数を使うよりも推奨されたやり方がありますが、今回は省略します)。

  • 入力エラーが発生した時の挙動
  • 最大件数($N=100000, K=100000$)の挙動

も確認しておきます。

test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include "minunit.h"
#define SOURCE "main"
#define IN_FILE "in.txt"
#define OUT_FILE "out.txt"
#define ERR_FILE "err.txt"

int tests_run = 0;
char result[1000000][8];

static char* message_u(size_t expected, size_t actual) {
	static char msg[80];
	sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
	return msg;
}

static char* message_s(const char *expected, const char *actual) {
	static char msg[80];
	sprintf(msg, "Error: expected: <%s> but was: <%s>", expected, actual);
	return msg;
}

static char* message_ss(size_t k, const char *expected, const char *actual) {
	static char msg[100];
	sprintf(msg, "[%zu] Error: expected: <%s> but was: <%s>", k, expected, actual);
	return msg;
}

static size_t test(size_t n, const char *lines[]) {
	static unsigned k = 0;
	FILE *file;
	if (!(file = fopen(IN_FILE, "w"))) {
		fprintf(stderr, "%s\n", strerror(errno));
		exit(errno);
	}
	for (size_t i = 0; i < n; i++) {
		fputs(lines[i], file);
		fputc('\n', file);
	}
	fclose(file);
	clock_t start = clock();
	if (!!(errno = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE))) {
		fprintf(stderr, "%s\n", strerror(errno));
		exit(errno);
	}
	fprintf(stderr, "#%u %f sec\n", ++k, (float) (clock() - start) / CLOCKS_PER_SEC);
	fflush(stderr);
	if (!(file = fopen(OUT_FILE, "r"))) {
		fprintf(stderr, "%s\n", strerror(errno));
		exit(errno);
	}
	size_t m = 0;
	while (fgets(result[m], sizeof(result[m]), file)) m++;
	fclose(file);
	return m;
}

static char* test0() {
	const char *lines[] = { "1 1", "0", "show" };
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_u(1, n), n == 1);
	mu_assert(message_s("0\n", result[0]), !strcmp(result[0], "0\n"));
	return 0;
}

static char* test1() {
	const char *lines[] = { "2 1", "1", "2", "show" };
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_u(2, n), n == 2);
	mu_assert(message_s("1\n", result[0]), !strcmp(result[0], "1\n"));
	mu_assert(message_s("2\n", result[1]), !strcmp(result[1], "2\n"));
	return 0;
}

static char* test2() {
	const char *lines[] = { "2 2", "1", "2", "pop", "show" };
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_u(1, n), n == 1);
	mu_assert(message_s("2\n", result[0]), !strcmp(result[0], "2\n"));
	return 0;
}

static char* test3() {
	const char *lines[] = {
		"100000  100000", "99999 100000", "0 0", "3 3",
		"10000000", "1000001", "1", "2", "3", "4",
		"pop", "pop", "show"
	};
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_u(1, n), n == 1);
	mu_assert(message_s("3\n", result[0]), !strcmp(result[0], "3\n"));
	return 0;
}

static char* test4() {
	static char *lines[200001] = { "100000 100000" };
	static char chars[100000][8] = {};
	for (size_t i = 1; i <= 100000; i++) {
		snprintf(chars[i], sizeof(chars[i]), "%zu", i % 10000 + 1);
		lines[i] = chars[i];
	}
	for (size_t i = 100001; i <= 200000; i++) {
		lines[i] = i % 10000 ? "pop" : "show";
	}
	size_t n = test(sizeof(lines) / sizeof(lines[0]), (const char**) lines);
	mu_assert(message_u(450055, n), n == 450055);
	size_t k = 0;
	for (size_t i = 1; i <= 10; i++) {
		for (size_t j = 9999 * i + 1; j <= 100000; j++) {
			char expected[8];
			snprintf(expected, sizeof(expected), "%zu\n", j % 10000 + 1);
			mu_assert(message_ss(k, expected, result[k]), !strcmp(result[k], expected));
			k++;
		}
	}
	return 0;
}

static char* all_tests() {
	mu_run_test(test0);
	mu_run_test(test1);
	mu_run_test(test2);
	mu_run_test(test3);
	mu_run_test(test4);
	return 0;
}

int main() {
	if (!!(errno = system("gcc -Wall -Wextra -Werror -DNDEBUG -std=c99 " SOURCE ".c UShortArray.c input.c -o " SOURCE " 1>" SOURCE ".txt 2>&1"))) {
		fprintf(stderr, "%s\n", strerror(errno));
		FILE* file = fopen(SOURCE ".txt", "r");
		if (file) {
			int c = 0;
			while ((c = fgetc(file)) != EOF) {
				putchar(c);
			}
			fclose(file);
		}
		return errno;
	}
	char *result = all_tests();
	if (result != 0) {
		fprintf(stderr, "%s\n", result);
		fprintf(stderr, "Tests run: %d\n", tests_run);
	} else {
		fprintf(stdout, "ALL TESTS PASSED\n");
		fprintf(stdout, "Tests run: %d\n", tests_run);
	}
	remove(ERR_FILE);
	remove(OUT_FILE);
	remove(IN_FILE);
	remove(SOURCE ".exe");
	remove(SOURCE ".txt");
	if (errno) {
		return errno;
	} else {
		return result != 0;
	}
}

test.bat
echo off
gcc -Wall -Wextra -Werror -std=c99 test.c -o test 1>test.txt 2>&1
if exist test.exe (
	.\test
	echo %ERRORLEVEL%
	del test.exe
	del test.txt
) else (
	type test.txt
)
pause
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?