K&Rを読もう(11) 演習1-17 - 18 やはり古典は読むべし。

K&Rを読むべきか!?と思っていたがやはり読んで正解だった。古典には良質な問題が付いている。解答も付いていないので、正解を探る楽しさもある。

生き残った古典には訳がある。やはり古典は読むべし。

演習 1-17

まずは肩慣らし、80文字以上の行を表示する。

しかし、デバッグが面倒なので10文字以上の文字を表示し、バッファサイズを20文字とした。getlineは省略する。

#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 20
#define OUTPUT_LENGTH 10

int  getline(char s[], int limit);
void copy(char to[], char from[]);

int main(void)
{
    int  len;
    int  max = 0;
    char line[MAXLINE];
    int c;

    while ((len = getline(line, MAXLINE)) > 0) {
        if (len == MAXLINE - 1 && line[len - 1] != '\n') {
            while((c = getchar()) != EOF) {
                len++;
                if (c == '\n')
                    break;
            }
        }
        if (len >= OUTPUT_LENGTH) {
            printf("%2d : %s", len,line);
            if (len >= MAXLINE)
                printf("...\n");
        }
    }

    exit(EXIT_SUCCESS);
}

テスト。

% ./ex-1-17 < ex-1-17.c
19 : #include <stdio.h>
20 : #include <stdlib.h>...
19 : #define MAXLINE 20
24 : #define OUTPUT_LENG...
...(略
32 :                 pri...
10 :         }
...(略

うまくいっている様子。

演習 1-18

良問。末尾のスペースと\tを取り除く。ついでに空行も取り除く。

#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 20

int remove_end_spaces(char s[], int len);
int  getline(char s[], int limit);
void copy(char to[], char from[]);

int main(void)
{
    int  len;
    int  max = 0;
    char line[MAXLINE];
    int c;

    while ((len = getline(line, MAXLINE)) > 0) {
        if (len == MAXLINE - 1 && line[len - 1] != '\n') {
            while((c = getchar()) != EOF) {
                len++;
                if (c == '\n')
                    break;
            }
        }
        len = remove_end_spaces(line ,len);
        if (len > 1) {
            printf("%2d : %s", len,line);
            if (len >= MAXLINE)
                printf("...\n");
        }
    }

    exit(EXIT_SUCCESS);
}

/* s/[ \t]*\n$/\n/ */
int remove_end_spaces(char s[], int len)
{
    int i;
    if (len <= 1) // 改行のみ
        return len;
    else if (s[len - 1] != '\n') // 改行が無い
        return len;

    for (i = len - 2; i >= 0 && (s[i] == ' ' || s[i] == '\t'); i--) { // 末尾\n前のスペースと\tを探す。
        /* 踏み潰す */
        s[i] = '\n';
        s[i + 1] = '\0';
        len--;
    }
    return len;
}
  • \nが必ず付いている訳では無いので除外してから作業にかかる。
  • 最後がスペースでMAXLINEに到達している場合は取り除いていない。
  • 末尾のスペースと\tを取り除くという正規表現で考えると、s/[ \t]*\n$/\n/になる。(chopしてないので\nが付く)
  • 踏み潰し法を使って末尾を切り詰めてみた。

結構楽しい。

ちと修正

returnが重複してるのが気になったので修正。

/* s/[ \t]*\n$/\n/ */
int remove_end_spaces(char s[], int len)
{
    int i = len - 1;
    if (i > 0 && s[i] == '\n') { /* 改行のチェック */
        /* スペースと\tを探す。 */
        for (i--; i >= 0 && (s[i] == ' ' || s[i] == '\t'); i--) {
            /* 踏み潰す */
            s[i] = '\n';
            s[i + 1] = '\0';
            len--;
        }
    }
    return len;
}
  • ついでにiをポインタのように使ってみた。C言語の文字処理はポインタを使った方がわかりやすい気がしたけど、今回はポインタを前に移動させる動きが必要なので、配列の方が操作しやすいかも。
  • 踏み潰す位置を変更して置換させたかったけど、うまい方法が思いつかず。

まとめ

  • 配列のインデックスって面倒だなぁと改めて実感。
  • 可変長文字列が恋しい。
  • 正規表現エンジンも面白そうだ(文字列苦手だからやりたくないけど)