paizaラーニングレベルアップ問題集の1つのデータの入力をやってみました。
問題
提出コード
良い子は真似しないでください。
#include <stdio.h>
int main(){
char s[102];
return fputs(fgets(s, sizeof(s), stdin), stdout) == EOF;
}
入力チェック
- $S$は1文字以上100文字以下の文字列
- $S$の各文字は英小文字または大文字または数字
を満たすか、チェックします。
プロトタイプ
input.h
#ifndef INPUT_H_
#define INPUT_H_
#include <stdbool.h>
bool input(const char*, char*);
#endif /* INPUT_H_ */
テスト
入力値に
- 0文字の文字列
- 101文字以上の文字列
- 英小文字、英大文字、数字以外の文字
が含まれる場合はNGとなることを確認します。
今回は、単体テストツールとしてminunit.hを使用します。
inputTest.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "minunit.h"
#include "input.h"
int tests_run = 0;
static char* message_s(const char *expected, const char *actual) {
static char msg[256];
snprintf(msg, sizeof(msg), "Error: expected: <%s> but was: <%s>", expected, actual);
return msg;
}
static char* message_d(int expected, int actual) {
static char msg[72];
snprintf(msg, sizeof(msg), "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static char* test_input(const char *str, const char *expected) {
char actual[strlen(str) + 1];
mu_assert("Error: expected: <true> but was: <false>", input(str, actual));
mu_assert(message_s(expected, actual), !strcmp(actual, expected));
mu_assert(message_d(0, errno), errno == 0);
return 0;
}
static char* test_input_invalid(const char *str) {
char actual[str ? strlen(str) + 1 : 0];
mu_assert("Error: expected: <false> but was: <true>", !input(str, actual));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_input_0() {return test_input_invalid(NULL);}
static char* test_input_1() {return test_input_invalid("");}
static char* test_input_2() {return test_input_invalid("\n");}
static char* test_input_3() {return test_input("0\n", "0");}
static char* test_input_4() {return test_input("9\n", "9");}
static char* test_input_5() {return test_input("A\n", "A");}
static char* test_input_6() {return test_input("Z\n", "Z");}
static char* test_input_7() {return test_input("a\n", "a");}
static char* test_input_8() {return test_input("z\n", "z");}
static char* test_input_9() {
const char str[] = { '0' - 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_10() {
const char str[] = { '9' + 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_11() {
const char str[] = { 'A' - 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_12() {
const char str[] = { 'Z' + 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_13() {
const char str[] = { 'a' - 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_14() {
const char str[] = { 'z' + 1, '\n', '\0' };
return test_input_invalid(str);
}
static char* test_input_15() {return test_input("00\n", "00");}
static char* test_input_16() {return test_input_invalid("_0\n");}
static char* test_input_17() {return test_input_invalid("0_\n");}
static char* test_input_18() {return test_input("000\n", "000");}
static char* test_input_19() {return test_input_invalid("_00\n");}
static char* test_input_20() {return test_input_invalid("0_0\n");}
static char* test_input_21() {return test_input_invalid("00_\n");}
static char* test_input_22() {
char str[102] = { };
memset(str, '0', 100);
str[100] = '\n';
char expected[101] = { };
memset(expected, '0', 100);
return test_input(str, expected);
}
static char* test_input_23() {
char str[103] = { };
memset(str, '0', 101);
str[101] = '\n';
return test_input_invalid(str);
}
static char* all_tests() {
mu_run_test(test_input_0);
mu_run_test(test_input_1);
mu_run_test(test_input_2);
mu_run_test(test_input_3);
mu_run_test(test_input_4);
mu_run_test(test_input_5);
mu_run_test(test_input_6);
mu_run_test(test_input_7);
mu_run_test(test_input_8);
mu_run_test(test_input_9);
mu_run_test(test_input_10);
mu_run_test(test_input_11);
mu_run_test(test_input_12);
mu_run_test(test_input_13);
mu_run_test(test_input_14);
mu_run_test(test_input_15);
mu_run_test(test_input_16);
mu_run_test(test_input_17);
mu_run_test(test_input_18);
mu_run_test(test_input_19);
mu_run_test(test_input_20);
mu_run_test(test_input_21);
mu_run_test(test_input_22);
mu_run_test(test_input_23);
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;
}
実装
- 入力値が
NULLの場合はfalseを返す - 入力文字列末尾の改行コードを削除する
- 入力文字列が末尾改行コード除いて101文字以上の場合は
falseを返す - 入力文字列が末尾改行コード除いて0文字の場合は
falseを返す - 入力文字列が英小文字、英大文字、数字以外の文字を含む場合は
falseを返す - 最後
trueを返す
ように実装します。このとき、上述のテストコードも全てパスします。
input.c
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "input.h"
char* chomp(char *s) {
if (!s)
return NULL;
size_t n = strlen(s);
while (n--)
if (isspace(s[n]))
s[n] = '\0';
else
break;
return s;
}
bool input(const char *str, char *s) {
errno = 0;
if (!(str && *str)) {
errno = EINVAL;
return false;
}
if (strlen(chomp(strcpy(s, str))) > 100) {
errno = EINVAL;
return false;
}
if (!*s) {
errno = EINVAL;
return false;
}
for (const char *c = s; *c; c++) {
if (!isalnum(*c)) {
errno = EINVAL;
return false;
}
}
return true;
}
Windows環境の場合、以下のバッチファイルを作成するとテストしやすいと思います。
echo off
gcc -Wall -Wextra -Werror -std=c99 inputTest.c input.c util.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関数の実装及びテストコード記述をします。
テスト
まず、main関数のテストコードを書きます。
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[1][128];
static char* message_zu(size_t expected, size_t actual) {
static char msg[72];
snprintf(msg, sizeof(msg), "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* message_s(const char *expected, const char *actual) {
static char msg[256];
snprintf(msg, sizeof(msg), "Error: expected: <%s> but was: <%s>", expected, actual);
return msg;
}
static char* message_d(int expected, int actual) {
static char msg[72];
snprintf(msg, sizeof(msg), "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static size_t test(size_t n, const char *lines[]) {
FILE *file;
if (!(file = fopen(IN_FILE, "w"))) {
perror(NULL);
exit(errno);
}
for (size_t i = 0; i < n; i++) {
fputs(lines[i], file);
fputc('\n', file);
}
fclose(file);
clock_t clockt = clock();
if (!!(errno = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE))) {
perror(NULL);
exit(errno);
}
fprintf(stderr, "#%d %f sec\n", tests_run, (float) (clock() - clockt) / CLOCKS_PER_SEC);
fflush(stderr);
if (!(file = fopen(OUT_FILE, "r"))) {
perror(NULL);
exit(errno);
}
size_t m = 0;
while (fgets(result[m], sizeof(result[m]), file)) m++;
fclose(file);
return m;
}
static int test_err(size_t n, const char *lines[]) {
FILE *file;
if (!(file = fopen(IN_FILE, "w"))) {
perror(NULL);
exit(errno);
}
for (size_t i = 0; i < n; i++) {
fputs(lines[i], file);
fputc('\n', file);
}
fclose(file);
clock_t clockt = clock();
int e = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE);
fprintf(stderr, "#%d %f sec\n", tests_run, (float) (clock() - clockt) / CLOCKS_PER_SEC);
fflush(stderr);
return e;
}
static char* test0() {
int e = test_err(0, NULL);
mu_assert(message_d(EXIT_FAILURE, e), e == EXIT_FAILURE);
return 0;
}
static char* test1() {
char s[64] = { };
size_t i = 0;
for (char c = '0'; c <= '9'; c++)
s[i++] = c;
for (char c = 'A'; c <= 'Z'; c++)
s[i++] = c;
for (char c = 'a'; c <= 'z'; c++)
s[i++] = c;
char t[64] = { };
strcpy(t, s);
strcat(t, "\n");
const char *lines[] = { s };
size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_zu(1, n), n == 1);
mu_assert(message_s(t, result[0]), !strcmp(result[0], t));
return 0;
}
static char* test2() {
char s[4][128] = { };
size_t i = 0;
for (char c = 0x20; c < 0x7F; c++)
s[1][i++] = c;
memset(s[2], '0', 101);
memset(s[3], '0', 100);
const char *lines[] = { s[0], s[1], s[2], s[3] };
char t[128] = { };
memset(t, '0', 100);
t[100] = '\n';
size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_zu(1, n), n == 1);
mu_assert(message_s(t, result[0]), !strcmp(result[0], t));
return 0;
}
static char* all_tests() {
mu_run_test(test0);
mu_run_test(test1);
mu_run_test(test2);
return 0;
}
int main() {
if (!!(errno = system("gcc -Wall -Wextra -Werror -DNDEBUG -std=c99 " SOURCE ".c input.c util.c -o " SOURCE " 1>" SOURCE ".txt 2>&1"))) {
perror(NULL);
FILE *file = fopen(SOURCE ".txt", "r");
if (file) {
int c = 0;
while ((c = fgetc(file)) != EOF)
putchar(c);
fclose(file);
}
exit(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;
}
}
実装
上述のinput関数を使ってmain関数を実装します。
-
EOFが入力されたらプログラムを終了します(Windowsなら「Ctrl+Z」) - 入力に不備のある場合は再入力となります
- コンパイル時に
-NDEBUGオプションを付けなければ時間計測モードになります(今回は何も実装していません) - (オプション)コマンドライン引数がある場合は、(特に入力チェックをせず)コマンドライン引数を出力します
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef NDEBUG
#include "input.h"
#include <errno.h>
const char END[] = { EOF, '\n', 0 };
#else
#include <time.h>
#define PRINT(format, ...) \
do { \
fprintf(stderr, format, ##__VA_ARGS__); \
fflush(stderr); \
} while (0)
#endif
int main(int argc, char *argv[]) {
#ifndef NDEBUG
clock_t clockt = clock();
#endif
char str[101] = { };
if (argc > 1) {
strncpy(str, argv[1], sizeof(str) - 1);
} else {
#ifdef NDEBUG
do {
errno = 0;
char s[102];
if (!fgets(s, sizeof(s), stdin)) {
return EXIT_FAILURE;
}
if (!strchr(s, '\n')) {
for (int c = getchar(); !strchr(END, c); c = getchar());
errno = EINVAL;
} else if (input(s, str)) {
break;
}
perror(
"The input must be at least 1 character and no more than 100 characters."
"Each character of the input value must be a lowercase letter, an uppercase letter, or a digit."
);
} while (true);
#endif
}
puts(str);
#ifndef NDEBUG
PRINT("%f sec.\n", (float) (clock() - clockt) / CLOCKS_PER_SEC);
#endif
return EXIT_SUCCESS;
}
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