C言語基礎|数字文字の出現回数

数字は10種類だけ! 文字列を走査して「0〜9の出現回数」を一気に見える化しよう

文字列の中身を「数えて理解」してみよう

文字列って、見た目はただの文字の並びですが、よく観察するといろんな情報が詰まっています。
その中でも分かりやすくて実用的なのが、数字文字 0〜9 がそれぞれ何回出てきたかを数えることです。

ここでは、文字列を先頭から順に走査して、数字文字を見つけたらカウントする、という王道の考え方を身につけます。
配列と条件判定、そして文字コードの性質('0' を引くと 0〜9 になる)をまとめて復習できる、おいしい題材です。

全体のアイデア:数える箱を10個用意する

数字文字は 0〜9 の 10種類だけなので、カウント用の配列を 10 要素で作るのがピッタリです。

カウント用配列の対応

数字文字添字カウント先
'0'0cnt[0]
'1'1cnt[1]
'2'2cnt[2]
.........
'9'9cnt[9]

表の説明

  • 添字が 0〜9 なので、数字文字と一致させると管理が簡単です
  • cnt[3] は「文字 '3' の出現回数」を入れる箱になります

なぜ cnt[s[i] - '0']++ で数えられるの?

C言語では文字は数値(文字コード)として扱われます。
そして '0','1',...,'9' は1つずつ増えていくことが保証されています。

文字から添字を作る仕組み

文字計算結果
'0''0' - '0'0
'3''3' - '0'3
'9''9' - '0'9

表の説明

  • '0' を基準に引き算すると、欲しい 0〜9 がそのまま出ます。
  • これができるのは「数字文字が連続している」保証があるからです。

サンプルプログラム

読み込みを scanf ではなく fgets にして、空白を含む行でも扱えるようにしています。

プロジェクト名:chap9-9-1 ソースファイル名:chap9-9-1.c

// 文字列内の数字文字をカウントする(別例)

#include <stdio.h>

// 文字列 s 内の数字文字の出現回数を cnt[10] に加算
void str_dcount(const char s[], int cnt[])
{
    int i = 0;
    while (s[i]) {
        if (s[i] >= '0' && s[i] <= '9')
            cnt[s[i] - '0']++;
        i++;
    }
}

int main(void)
{
    char line[128];
    int dcnt[10] = {0};

    printf("一行入力してください(例:ID=A12-39):");
    fgets(line, sizeof(line), stdin);

    str_dcount(line, dcnt);

    puts("数字の出現回数まとめ");
    for (int i = 0; i < 10; i++)
        printf("%d : %d回\n", i, dcnt[i]);

    return 0;
}

実行例

一行入力してください(例:ID=A12-39):ID=Q12-39
数字の出現回数まとめ
0 : 0回
1 : 1回
2 : 1回
3 : 1回
4 : 0回
5 : 0回
6 : 0回
7 : 0回
8 : 0回
9 : 1回

文字列を走査して数える流れ

走査とカウントの流れ(イメージ)

図の説明

  • 先頭から1文字ずつ見ます。
  • 数字なら cnt の該当箱を1増やします。
  • \0(終端)に来たら走査終了です。

登場する命令の書式と「何をする命令か」

while の書式と役割

  • 書式
    while (条件式) 文;
  • 何をする?
    条件式が真のあいだ、文を繰り返します。
    今回は s[i] が 0(終端)になるまで文字を見続けます。

if の書式と役割

  • 書式
    if (条件式) 文;
  • 何をする?
    条件式が真のときだけ文を実行します。
    今回は s[i] が '0'〜'9' の範囲ならカウントします。

fgets の書式と役割

  • 書式
    char *fgets(char *s, int size, FILE *stream);
  • 何をする?
    1行読み込んで配列 s に入れます(改行も入ることがあります)。
    scanf の %s と違って、空白を含む入力も読み取れます。

puts の書式と役割

  • 書式
    int puts(const char *s);
  • 何をする?
    文字列を表示して、最後に改行も付けます。見出し表示に便利です。

printf の書式と役割

  • 書式
    int printf(const char *format, ...);
  • 何をする?
    書式に従って整形しながら表示します。今回は集計結果の表示に使います。

cnt 配列を 0 で初期化する意味

カウントは「足し算」で増えていくので、最初が 0 じゃないと正しく数えられません。

初期化の考え方

状態dcnt[10] の中身結果
初期化あり全部 0 から開始正しく数えられる
初期化なしゴミ値の可能性いきなり変な回数になる

表の説明

  • int dcnt[10] = {0}; は「全要素を 0 にする」定番テクニックです

演習問題

演習9-8:文字列を後ろから表示する関数

文字列を後ろから逆に表示する関数を作成せよ。
void put_stringr(const char s[]);

解答例

プロジェクト名:chap9-9-2 ソースファイル名:chap9-9-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

int str_length(const char s[])
{
    int len = 0;
    while (s[len])
        len++;
    return len;
}

void put_stringr(const char s[])
{
    int len = str_length(s);
    for (int i = len - 1; i >= 0; i--)
        putchar(s[i]);
}

int main(void)
{
    char s[128];

    printf("文字列:");
    scanf("%127s", s);

    put_stringr(s);
    putchar('\n');

    return 0;
}

解説

  • まず長さを調べて、末尾から添字を減らして表示します。
  • \0 は表示しません(走査の終端として使うだけです)

演習9-9:文字列そのものを反転して更新する関数

文字列 s の並びを反転する関数を作成せよ。
void rev_string(char s[]);

解答例

プロジェクト名:chap9-9-3 ソースファイル名:chap9-9-3.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

int str_length(const char s[])
{
    int len = 0;
    while (s[len])
        len++;
    return len;
}

void rev_string(char s[])
{
    int len = str_length(s);
    for (int i = 0; i < len / 2; i++) {
        char tmp = s[i];
        s[i] = s[len - 1 - i];
        s[len - 1 - i] = tmp;
    }
}

int main(void)
{
    char s[128];

    printf("文字列:");
    scanf("%127s", s);

    rev_string(s);

    printf("反転結果:%s\n", s);

    return 0;
}

解説

  • 左端と右端を交換して、内側へ詰めていく方式です。
  • len/2 回だけ交換すれば全部ひっくり返ります。
  • 更新するので仮引数は const を付けません。