0022. 階乗・順列・組み合わせの計算(C言語)

戻る

factpercom.cのソースコード】


/*
 * 階乗・順列・組み合わせを計算するプログラム
 * factpercom.c
 * 
 * 階乗: factorial、順列: permutation、組み合わせ: combination
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strlen(), strcmp(), strcpy(), strcat() */
#include <ctype.h>  /* isdigit() */
#include <time.h>

#define BUF_SIZE 256
#define MAX_VALUE 9223372036854775807ULL /* unsigned long long int の最大値 */





/*
 * 自作のbool型の定義
 */
typedef enum {
    false, /* 0 */
    true   /* 1 */
} bool;



/*
 * 各分岐処理のラベル付け
 */
enum {
    do_end, /* 0: プログラムの終了 */
    fact,   /* 1: 階乗             */
    perm,   /* 2: 順列             */
    comb,   /* 3: 組み合わせ       */
};





/*
 * ユーザー定義関数の関数原型宣言
 */
void main_menu(void);
void main_proc(void);
void print_title(void);
void input(void);
void calc(void);
void comma_sep(char *sp, unsigned long long int n);
unsigned long long int factorial(int n);
unsigned long long int permutation(int n, int r);
unsigned long long int combination(int n, int r);
bool is_overflow(unsigned long long int value);
void output(void);
void output_result(char strans[]);
void foutput_result(char filename[], char oldfilename[], unsigned long long int ans);
void switch_output(char strans[], unsigned long long int value);
void fswitch_output(FILE *fp, unsigned long long int value);
void gen_filename(char filename[]);
bool retry(void);





/*
 * グローバル変数
 */
char strans[BUF_SIZE],
     filename[BUF_SIZE]    = {1}, /* filenameとoldfilenameの初期値を */
     oldfilename[BUF_SIZE] = {0}; /* 異なる物に設定                  */
                                  /* (ファイルへの追記の判断に使用)  */
int proc_num, n, r;
unsigned long long int ans;





/*
 * メイン関数
 */
int main(void)
{
    /* 乱数系列の設定 */
    srand((unsigned int)time(NULL));
    
    while (1) {
        /* メインメニュー(処理の選択) */
        main_menu();
        
        /* 実処理 */
        main_proc();
        
        /* 処理を続けるかどうかの選択 */
        if (!retry())
            break;
    }
    
    return EXIT_SUCCESS;
}





/*
 * メインメニュー部の関数
 */
void main_menu(void)
{
    char buf[BUF_SIZE], ss[BUF_SIZE], ch, zz;
    
    printf("【階乗・順列・組み合わせを計算します】\n");
    printf("次のいずれかを選択してください。\n\n");
    
    while (1) {
        printf("1.階乗を計算する。\n");
        printf("2.順列を計算する。\n");
        printf("3.組み合わせを計算する。\n");
        printf("0.終了する。\n\n");
        printf("0~3のどれですか? ");
        
        fgets(buf, sizeof buf, stdin);
        sscanf(buf, "%c%c", &ch, &zz);
        
        if (((ch != '1') && (ch != '2') && (ch != '3') && (ch != '0'))
         || (zz != '\n')) {
            printf("無効な入力です。\n");
            printf("0、1、2、3のいずれかを入力しください。\n\n");
            continue;
        } else
            break;
    }
    
    /* 文字chを数値proc_numに変換する */
    ss[0] = ch; ss[1] = '\0';
    proc_num = atoi(ss);
}



/*
 * 実処理を行う関数
 * print_title()関数、input()関数、calc()関数を使用する。
 */
void main_proc(void)
{
    /* 表題の表示 */
    print_title();
    
    /* 入力 */
    input();
    
    /* 計算 */
    calc();
    
    /* 出力 */
    output();
}



/*
 * 各処理の表題を表示する関数
 */
void print_title(void)
{
    switch (proc_num) {
    
     /* 処理(1/3):階乗の場合 */
     case fact:
        printf("\n【階乗n!を計算します】\n\n");
        printf("nを入力してください。\n");
        printf("(ただし、0 ≦ nとする)\n\n");
        break;
    
     /* 処理(2/3):順列の場合 */
     case perm:
        printf("\n【順列nPrを計算します】\n\n");
        printf("nおよびrを、順に入力してください。\n");
        printf("(ただし、0 ≦ r ≦ nとする)\n\n");
        break;
    
     /* 処理(3/3):組み合わせの場合 */
     case comb:
        printf("\n【組み合わせnCrを計算します】\n\n");
        printf("nおよびrを、順に入力してください。\n");
        printf("(ただし、0 ≦ r ≦ nとする)\n\n");
        break;
    
     /* 処理(0/3):終了の場合 */
     case do_end:
        exit(EXIT_SUCCESS);
        break;
    
     default:
        break;
    }
}



/*
 * 各処理中の入力部の関数
 * is_valid_number()関数を使用する。
 */
void input(void)
{
    char buf[BUF_SIZE], zz;
    
    switch (proc_num) {
    
     /* 処理(1/3):階乗の場合 */
     case fact:
        while (1) {
            printf("n = ? ");
            fgets(buf, sizeof buf, stdin);
            sscanf(buf, "%d%c", &n, &zz);
            buf[strlen(buf) - 1] = '\0';
            if (!is_valid_number(buf)) {
                printf("無効な入力です。\n");
                printf("半角整数値を入力してください。\n\n");
                continue;
            } else if (n < 0) {
                printf("無効な入力です。\n");
                printf("0以上の整数を入力してください。\n\n");
            } else
                break;
        }
        break;
    
     /* 処理(2/3):順列の場合 */
     case perm:
        while (1) {
            
            /* nの入力 */
            while (1) {
                printf("n = ? ");
                fgets(buf, sizeof buf, stdin);
                sscanf(buf, "%d%c", &n, &zz);
                buf[strlen(buf) - 1] = '\0';
                if (!is_valid_number(buf)) {
                    printf("無効な入力です。\n");
                    printf("半角整数値を入力してください。\n\n");
                    continue;
                } else if (n < 0) {
                    printf("無効な入力です。\n");
                    printf("0以上の整数を入力してください。\n\n");
                } else
                    break;
            }
            
            /* rの入力 */
            while (1) {
                printf("r = ? ");
                fgets(buf, sizeof buf, stdin);
                sscanf(buf, "%d%c", &r, &zz);
                buf[strlen(buf) - 1] = '\0';
                if (!is_valid_number(buf)) {
                    printf("無効な入力です。\n");
                    printf("半角整数値を入力してください。\n\n");
                    continue;
                } else if (r < 0) {
                    printf("無効な入力です。\n");
                    printf("0以上の整数を入力してください。\n\n");
                } else
                    break;
            }
            
            /* 「0≦r≦n」を満たさない場合の処理 */
            if ((!(0 <= r)) || (!(r <= n))) {
                printf("「0 ≦ r ≦ n」ではありません。\n");
                printf("「0 ≦ r ≦ n」となるように入力してください。\n");
                continue;
            } else
                break;
        }
        break;
    
     /* 処理(3/3):組み合わせの場合 */
     case comb:
        while (1) {
            
            /* nの入力 */
            while (1) {
                printf("n = ? ");
                fgets(buf, sizeof buf, stdin);
                sscanf(buf, "%d%c", &n, &zz);
                buf[strlen(buf) - 1] = '\0';
                if (!is_valid_number(buf)) {
                    printf("無効な入力です。\n");
                    printf("半角整数値を入力してください。\n\n");
                    continue;
                } else if (n < 0) {
                    printf("無効な入力です。\n");
                    printf("0以上の整数を入力してください。\n\n");
                } else
                    break;
            }
            
            /* rの入力 */
            while (1) {
                printf("r = ? ");
                fgets(buf, sizeof buf, stdin);
                sscanf(buf, "%d%c", &r, &zz);
                buf[strlen(buf) - 1] = '\0';
                if (!is_valid_number(buf)) {
                    printf("無効な入力です。\n");
                    printf("半角整数値を入力してください。\n\n");
                    continue;
                } else if (r < 0) {
                    printf("無効な入力です。\n");
                    printf("0以上の整数を入力してください。\n\n");
                } else
                    break;
            }
            
            /* 「0≦r≦n」を満たさない場合の処理 */
            if ((!(0 <= r)) || (!(r <= n))) {
                printf("「0 ≦ r ≦ n」ではありません。\n");
                printf("「0 ≦ r ≦ n」となるように入力してください。\n");
                continue;
            } else
                break;
        }
        break;
    
     default:
        break;
    }
}



/*
 * 文字列が正しい形式の数値かどうかを判定する関数
 * true: 正しい形式、false: 無効な形式
 */
bool is_valid_number(char buf[])
{
    int i = 0;
    
    /* 最初は符号(省略可) */
    if (buf[i] == '+' || buf[i] == '-')
        i++;
    /* 次に数字(省略不可) */
    if (!isdigit(buf[i]))
        return false;
    i++;
    /* 次に数字列(省略可) */
    while (isdigit(buf[i]))
        i++;
    
    return buf[i] == '\0';
}



/*
 * 各処理中の計算部の関数
 * factorial()関数、comma_sep()関数、permutaion()関数、
 * およびcombination()関数を使用する。
 */
void calc(void)
{
    switch (proc_num) {
    
     /* 処理(1/3):階乗の場合 */
     case fact:
        /* 階乗の計算 */
        ans = factorial(n);
        
        /* 計算結果を3桁ずつコンマで区切られた文字列に変換 */
        comma_sep(strans, ans);
        break;
    
     /* 処理(2/3):順列の場合 */
     case perm:
        /* 順列の計算 */
        ans = permutation(n, r);
        
        /* 計算結果を3桁ずつコンマで区切られた文字列に変換 */
        comma_sep(strans, ans);
        break;
    
     /* 処理(3/3):組み合わせの場合 */
     case comb:
        /* 組み合わせの計算 */
        ans = combination(n, r);
        
        /* 計算結果を3桁ずつコンマで区切られた文字列に変換 */
        comma_sep(strans, ans);
        break;
     default:
        break;
    }
}



/*
 * 数値を3桁ずつコンマで区切られた文字列に変換する関数
 */
void comma_sep(char *sp, unsigned long long int n)
{
    char *p = sp,        /* 作業用のポインタpを宣言し、spに初期化 */
         c,              /* 文字入れ換え時の作業用変数            */
         ss_n[BUF_SIZE]; /* nの10進数表記の文字列の格納用変数     */
    int digit = 0,       /* 10進桁数のカウント用の変数            */
        len;             /* nの10進数表記での桁数の格納用変数     */
    
    /* nの10進数表記での桁数をlenに取得 */
    sprintf(ss_n, "%llu", n);
    len = strlen(ss_n);
    
    /* nが0の場合は文字0をp(=sp)に格納 */
    if (n == 0)
        sprintf(p, "0");
    
    /* nが0より大の場合、1の位からコンマで区切ってp(=sp)に格納 */
    else {
        while (n > 0) {
            sprintf(p++, "%llu", n % 10);
            n /= 10;
            digit++;
            if (digit % 3 == 0 && digit != len)
                sprintf(p++, ",");
        }
        *p-- = '\0';
    }
    
    /* 文字列を反転 */
    while (sp < p) {
        c = *sp;
        *sp++ = *p;
        *p-- = c;
    }
}



/*
 * 階乗を計算する関数
 * is_overflow()関数を使用する。
 */
unsigned long long int factorial(int n)
{
    int i;
    unsigned long long int ans = 1;
    
    if (n == 0)
        ;
    else
        for (i = 1; i <= n; i++) {
            ans *= i;
            if (is_overflow(ans))
                return MAX_VALUE + 1;
        }
    
    return ans;
}



/*
 * 順列を計算する関数
 * is_overflow()関数を使用する。
 */
unsigned long long int permutation(int n, int r)
{
    int i;
    unsigned long long int ans = 1;
    
    if ((n == 0) || (r == 0))
        ;
    else
        for (i = 0; i < r; i++) {
            ans *= (n - i);
            if (is_overflow(ans))
                return MAX_VALUE + 1;
        }
    
    return ans;
}



/*
 * 組み合わせを計算する関数
 * is_overflow()関数を使用する。
 */
unsigned long long int combination(int n, int r)
{
    int i;
    unsigned long long int ans = 1;
    
    if (r > n - r)
        r = n - r;
    
    if ((n == 0) || (r == 0))
        ;
    else
        for (i = 1; i <= r; i++) {
            ans *= (n - r + i);
            if (is_overflow(ans))
                return MAX_VALUE + 1;
            ans /= i;
        }
    
    return ans;
}



/*
 * オーバーフローの判定をする関数
 * true: オーバーフロー、false: オーバーフローしていない。
 */
bool is_overflow(unsigned long long int value)
{
    return value > MAX_VALUE;
}



/*
 * 各処理中の出力部の関数
 * output_result()関数とfoutput_result()関数を使用する。
 */
void output(void)
{
    char buf[BUF_SIZE], ch, zz;
    
    output_result(strans);
    
    while (1) {
        printf("ファイルに出力しますか?(y/n) ");
        
        fgets(buf, sizeof buf, stdin);
        sscanf(buf, "%c%c", &ch, &zz);
        
        if (((ch == 'y') || (ch == 'Y')) && (zz == '\n')) {
            foutput_result(filename, oldfilename, ans);
            break;
        } else if (((ch == 'n') || (ch == 'N')) && (zz == '\n'))
            break;
        else {
            printf("無効な入力です。\n");
            printf("yまたはnを入力してください。\n\n");
            continue;
        }
    }
}



/*
 * 各計算結果を画面に出力する関数
 * switch_output()関数を使用する。
 */
void output_result(char strans[])
{
    switch (proc_num) {
    
     /* 処理(1/3):階乗の場合 */
     case fact:
        printf("%d! = ", n);
        switch_output(strans, ans);
         printf("\n");
         break;
    
     /* 処理(2/3):順列の場合 */
     case perm:
        printf("%dP%d = ", n, r);
        switch_output(strans, ans);
        printf("\n");
         break;
    
     /* 処理(3/3):組み合わせの場合 */
     case comb:
        printf("%dC%d = ", n, r);
        switch_output(strans, ans);
        printf("\n");
         break;
    
     default:
         break;
    }
}



/*
 * 各計算結果をファイルに出力する関数
 * gen_filename()関数、fswitch_output()を使用する。
 */
void foutput_result(char filename[], char oldfilename[], unsigned long long int ans)
{
    FILE *fp;
    
    /* ifによる分岐は、2行目以降を同一ファイルに追記出力させるため                          */
    if (strcmp(filename, oldfilename) == 0) { /* ファイル名が同じなら(2行目以降なら)        */
        fp = fopen(oldfilename, "a");         /* oldfilenameを開く                          */
        if (fp == NULL) {
            printf("ファイルを開けません。ファイル出力を中断します。\n");
            return;
        }
    } else {                                  /* ファイル名が違うなら(初めてのファイルなら) */
        gen_filename(filename);               /* 新規にファイル名を生成させて               */
        fp = fopen(filename, "a");            /* そのファイル(filename)を開いて             */
        strcpy(oldfilename, filename);        /* oldfilenameを更新する                      */
        if (fp == NULL) {
            printf("ファイルを開けません。ファイル出力を中断します。\n");
            return;
        }
    }
    
    switch (proc_num) {
    
     /* 処理(1/3):階乗の場合 */
     case fact:
        fprintf(fp, "%d!,", n);
        fswitch_output(fp, ans);
        fprintf(fp, "\n");
         break;
    
     /* 処理(2/3):順列の場合 */
     case perm:
        fprintf(fp, "%dP%d,", n, r);
        fswitch_output(fp, ans);
        fprintf(fp, "\n");
         break;
    
     /* 処理(3/3):組み合わせの場合 */
     case comb:
        fprintf(fp, "%dC%d,", n, r);
        fswitch_output(fp, ans);
        fprintf(fp, "\n");
         break;
    
     default:
         break;
    }
    
    fclose(fp);
}



/*
 * 場合に応じて形式を切り替えて画面出力する関数
 */
void switch_output(char strans[], unsigned long long int ans)
{
    /* オーバーフローの場合 */
    if (ans == MAX_VALUE + 1)
        printf("#NUM!");
    
    /* 計算結果が10億以上の場合は指数形式で出力する */
    else if (ans >= 1000000000)
        printf("%LE", (long double)ans);
    
    /* それ以外の場合は3桁ずつコンマで区切られた整数形式で出力する */
    else
        printf("%s", strans);
}



/*
 * 場合に応じて形式を切り替えてファイル出力する関数
 */
void fswitch_output(FILE *fp, unsigned long long int ans)
{
    /* オーバーフローの場合 */
    if (ans == MAX_VALUE + 1)
        fprintf(fp, "#NUM!");
    
    /* 計算結果が10億以上の場合は指数形式で出力する */
    else if (ans >= 1000000000)
        fprintf(fp, "%LE", (long double)ans);
    
    /* それ以外の場合は整数形式で出力する */
    else
        fprintf(fp, "%llu", ans);
}



/*
 * 現在日付時刻からファイル名を生成させる関数
 */
void gen_filename(char filename[])
{
    struct tm *t;
    time_t tt;
    char strnumrand[10];
    
    time(&tt);
    t = localtime(&tt);
    
    sprintf(filename, "factpercom%04d%02d%02d_%02d%02d%02d_",
     1900+(t->tm_year), 1+(t->tm_mon), t->tm_mday,
     t->tm_hour, t->tm_min, t->tm_sec);
    sprintf(strnumrand, "%04d", rand() % 10000);
    filename = strcat(filename, strnumrand);
    filename = strcat(filename, ".csv");
}



/*
 * 操作を続けるかどうかを選択する関数
 * true: 続ける、false: 終了する
 */
bool retry(void)
{
    bool ret;
    char buf[BUF_SIZE], ch, zz;
    
    while (1) {
        printf("\n続けますか?(y/n) ");
        
        fgets(buf, sizeof buf, stdin);
        sscanf(buf, "%c%c", &ch, &zz);
        
        if (((ch == 'y') || (ch == 'Y')) && (zz == '\n')) {
            printf("\n+++++\n\n");
            ret = true;
            break;
        } else if (((ch == 'n') || (ch == 'N')) && (zz == '\n')) {
            ret = false;
            break;
        }
        printf("無効な入力です。\n");
        printf("yまたはnを入力してください。\n\n");
    }
    
    return ret;
}


factpercomの実行時画面表示】赤字はキーボードからの入力を表す。

C:\Users\skonishi\Documents>factpercom
【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 1

【階乗n!を計算します】

nを入力してください。
(ただし、0 ≦ nとする)

n = ? 10
10! = 3,628,800                                   ←画面出力では数値が3桁ずつコンマで区切って表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 2

【順列nPrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 10
r = ? 10
10P10 = 3,628,800                                 ←画面出力では数値が3桁ずつコンマで区切って表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 3

【組み合わせnCrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 20
r = ? 10
20C10 = 184,756                                   ←画面出力では数値が3桁ずつコンマで区切って表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 1

【階乗n!を計算します】

nを入力してください。
(ただし、0 ≦ nとする)

n = ? 15
15! = 1.307674E+12                                ←10億(1E+9)を超えると指数形式で表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 2

【順列nPrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 15
r = ? 15
15P15 = 1.307674E+12                              ←10億(1E+9)を超えると指数形式で表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 3

【組み合わせnCrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 40
r = ? 20
40C20 = 1.378465E+11                              ←10億(1E+9)を超えると指数形式で表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 1

【階乗n!を計算します】

nを入力してください。
(ただし、0 ≦ nとする)

n = ? 30
30! = #NUM!                                       ←オーバーフローすると「#NUM!」と表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 2

【順列nPrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 30
r = ? 30
30P30 = #NUM!                                     ←オーバーフローすると「#NUM!」と表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) y

+++++

【階乗・順列・組み合わせを計算します】
次のいずれかを選択してください。

1.階乗を計算する。
2.順列を計算する。
3.組み合わせを計算する。
0.終了する。

0~3のどれですか? 3

【組み合わせnCrを計算します】

nおよびrを、順に入力してください。
(ただし、0 ≦ r ≦ nとする)

n = ? 70
r = ? 35
70C35 = #NUM!                                     ←オーバーフローすると「#NUM!」と表示されている。
ファイルに出力しますか?(y/n) y

続けますか?(y/n) n


【出力されたファイルfactpercom20250307_194941_5490.csvの内容】

10!,3628800                                       ←ファイル出力では数値データがそのまま(コンマで区切られずに)出力されている。
10P10,3628800                                     ←ファイル出力では数値データがそのまま(コンマで区切られずに)出力されている。
20C10,184756                                      ←ファイル出力では数値データがそのまま(コンマで区切られずに)出力されている。
15!,1.307674E+12
15P15,1.307674E+12
40C20,1.378465E+11
30!,#NUM!
30P30,#NUM!
70C35,#NUM!


【出力されたファイルfactpercom20250307_194941_5490.csvをエクセルで開いた結果】
エクセルで開いた結果

【参考文献】
・林晴比古、「C言語による実用アルゴリズム入門」、ソフトバンクパブリッシング(株)・2004年発行。
戻る