在 C 中每第 n 列(K&R 1-22)折叠输入行

Folding input lines every nth column (K&R 1-22) in C

Write a program to "fold" long input lines into two or more shorter lines after the last non-blank character that occurs before the n-th column of input. Make sure your program does something intelligent with very long lines, and if there are no blanks or tabs before the specified column.

我决定遵循的算法如下:

  1. 如果输入行的长度 < maxcol(必须在其后折叠的列),则按原样打印该行。
  2. 如果不是,我从maxcol开始往左看,往右看最接近的非space字符,保存为'first'和'last'。然后我将字符数组从 line[0] 打印到 line[first],然后数组的其余部分,从 line[last] 到 line[len] 成为新的行数组。

这是我的代码:

#include <stdio.h>

#define MAXCOL 5

int getline1(char line[]);

int main()
{
    char line[1000];
    int len, i, j, first, last;

    len = getline1(line);

    while (len > 0) {
        if (len < MAXCOL) {
            printf("%s\n", line);
            break;
        }
        else {
            for (i = MAXCOL - 1; i >= 0; i--) {
                if (line[i] != ' ') {
                    first = i; 
                    break;
                }
            }
            for (j = MAXCOL - 1; j <= len; j++) {
                if (line[j] != ' ') {
                    last = j; 
                    break;
                }
            }
            //printf("first %d last %d\n", first, last);
            for (i = 0; i <= first; i++) 
                putchar(line[i]);
            putchar('\n');
            for (i = 0; i < len - last; i++) {
                line[i] = line[last + i];
            }
            len -= last;
            first = last = 0;
        }
    }
    return 0;
}

int getline1(char line[])
{
    int c, i = 0;

    while ((c = getchar()) != EOF && c != '\n') 
        line[i++] = c;

    if (c == '\n')
        line[i++] = '\n';

    line[i] = '[=10=]';

    return i;
}

问题如下:

例如,输入:

asd        de             def          deffff

我得到输出:

asd
de
def
defff //Expected until here
//Unexpected lines below
ff
fff
      deffff
        deffff
    deffff

问题 1 - 为什么会打印意外的线条?如何让我的 program/algorithm 变得更好?

最终,在花了很多时间解决这个问题之后,我放弃了并决定查看 clc-wiki 的解决方案。这里的每个程序都 NOT 工作,保存一个(其他的没有工作,因为它们没有涵盖某些边缘情况)。起作用的是最大的那个,对我来说没有任何意义。它没有任何注释,我也无法正确理解变量名及其代表的含义。但维基中的 ONLY 程序起作用了。

#include <stdio.h>

#define YES 1
#define NO 0

int main(void)
{
  int TCOL = 8, ch, co[3], i, COL = 19, tabs[COL - 1];
  char bls[COL - 1], bonly = YES;

  co[0] = co[1] = co[2] = 0;

  while ((ch = getchar()) != EOF)
  {
      if (ch != '\t') {
          ++co[0];
          ++co[2];
      }

      else {
          co[0] = co[0] + (TCOL * (1 + (co[2] / TCOL)) - co[2]);
          i = co[2];
          co[2] = TCOL + (co[2] / TCOL) * TCOL;
      }

      if (ch != '\n' && ch != ' ' && ch != '\t')
      {
          if (co[0] >= COL) {
              putchar('\n');

              co[0] = 1;
              co[1] = 0;
          }

          else
              for (i = co[1]; co[1] > 0; --co[1])
              {
                  if (bls[i - co[1]] == ' ')
                      putchar(bls[i - co[1]]);

                  else
                      for (; tabs[i - co[1]] != 0;)

                          if (tabs[i - co[1]] > 0) {
                              putchar(' ');
                              --tabs[i - co[1]];
                          }

                          else {
                              tabs[i - co[1]] = 0;
                              putchar(bls[i - co[1]]);
                          }
              }

          putchar(ch);

          if (bonly == YES)
              bonly = NO;
      }

      else if (ch != '\n')
      {
          if (co[0] >= COL)
          {
              if (bonly == NO) {
                  putchar('\n');

                  bonly = YES;
              }

              co[0] = co[1] = 0;
          }

          else if (bonly == NO) {
              bls[co[1]] = ch;

              if (ch == '\t') {

                  if (TCOL * (1 + ((co[0] - (co[2] - i)) / TCOL)) -
                    (co[0] - (co[2] - i)) == co[2] - i)
                      tabs[co[1]] = -1;

                  else
                      tabs[co[1]] = co[2] - i;
              }

              ++co[1];
          }

          else
              co[0] = co[1] = 0;
      }

      else {
          putchar(ch);

          if (bonly == NO)
              bonly = YES;

          co[0] = co[1] = co[2] = 0;
      }
  }

  return 0;
}

问题 2 - 你能帮我理解这段代码及其工作原理吗?

它修复了我的解决方案中的所有问题,并且还通过逐字符读取来工作,因此看起来更有效率。

Question 1 - Why do the unexpected lines print? How do I make my program/algorithm better?

您在输出中得到了意外的行,因为在打印数组后,您没有用空字符 [=18=] -

终止新的 line 数组

这里你正在复制从 lastlen - last 的字符,创建一个新的 line 数组:

        for (i = 0; i < len - last; i++) {
            line[i] = line[last + i];
        }

您复制了字符,但空终止字符仍在其原始位置。假设输入字符串为:

asd        de             def          deffff

所以,最初 line 数组的内容将是:

"asd        de             def          deffff\n"
                                                ^
                                                |
                                  null character is here

现在在打印 asd 之后,您正在将字符从 linelast 索引复制到 len - last 索引到 line 数组本身,从 0索引。因此,复制 line 数组的内容后将是:

"de             def          deffff\n    deffff\n"
                                     |____  _____|
                                          \/
                                This is causing the unexpected output 
                            (null character is still at the previous location)

因此,在 for 循环之后,您应该在复制的最后一个字符之后添加空字符,如下所示:

line [len - last] = '[=14=]';

这样 line 数组的内容将在 while 循环的下一次迭代中处理:

"de             def          deffff\n"

还有一件事,在 line 数组中,您可以在末尾看到 \n(换行符)字符。如果你想在处理输入之前删除它,你可以这样做:

line[strcspn(line, "\n")] = 0;

您可以在程序中进行的改进:
1. 您可以做的一个非常明显的改进是在处理输入字符串时使用指向输入字符串的指针。在指针的帮助下,您不需要将数组的其余部分(除了已处理的部分)再次复制到同一个数组,直到程序处理整个输入。将指针初始化为输入字符串的开头,在每次迭代中只需将指针移动到适当的位置并从指针指向的位置开始处理。
2. 由于您首先将整个输入放入缓冲区然后进行处理。您可以考虑 fgets() 来接受输入。它将更好地控制用户的输入。
3. 添加对 line 数组溢出的检查,以防输入非常长。使用 fgets() 您可以指定要从输入流复制到 line 数组的最大字符数。

Question 2 - Can you help me make sense of this code and how it works?

程序非常简单,自己尝试至少理解一次。使用调试器或拿笔和纸,干燥 运行 一次用于小尺寸输入并检查输出。增加输入大小并添加一些变体,例如多个 space 字符,并检查程序代码路径和输出。这样你就很容易理解了。

这是此练习的另一个(我认为更好的)解决方案:

#include <stdio.h>

#define MAXCOL 10

void my_flush(char buf[]);

int main()
{
  int c, prev_char, i, j, ctr, spaceleft, first_non_space_buf;
  char buf[MAXCOL+2];

  prev_char = -1;
  i = first_non_space_buf = ctr = 0;
  spaceleft = MAXCOL;

  printf("Just keep typing once the output has been printed");

  while ((c = getchar()) != EOF) {
    if (buf[0] == '\n') {
      i = 0;
      my_flush(buf);
    }
    //printf("Prev char = %c and Current char = %c and i = %d and fnsb = %d and spaceleft = %d and j = %d and buf = %s \n", prev_char, c, i, first_non_space_buf, spaceleft, j,  buf);
    if ((((prev_char != ' ') && (prev_char != '\t') && (prev_char != '\n')) &&
     ((c == ' ') || (c == '\t') || (c == '\n'))) ||
    (i == MAXCOL)) {
      if (i <= spaceleft) {
    printf("%s", buf);
    spaceleft -= i;
      }
      else {
    putchar('\n');
    spaceleft = MAXCOL;
    for (j = first_non_space_buf; buf[j] != '[=10=]'; ++j) {
      putchar(buf[j]);
      ++ctr;
    }
    spaceleft -= ctr;
      }
      i = 0;
      my_flush(buf);
      buf[i++] = c;
      first_non_space_buf = j = ctr = 0;
    }
    else {
      if (((prev_char == ' ') || (prev_char == '\t') || (prev_char == '\n')) &&
      ((c != ' ') && (c != '\t') && (c != '\n'))) {
    first_non_space_buf = i;
      }
      buf[i++] = c;
      buf[i] = '[=10=]';
    }
    prev_char = c;
  }
  printf("%s", buf);
  return 0;
}

void my_flush(char buf[])
{
  int i;

  for (i = 0; i < MAXCOL; ++i)
    buf[i] = '[=10=]';
}

下面是我的解决方案,我知道线程不再活跃,但我的代码可能会帮助遇到问题的人掌握已经提供的代码片段。

*编辑

解释

  • 继续读取输入,除非输入包含“\n”、“\t”或 至少 MAXCOl 个字符。
  • Incase of '\t',使用expandTab替换所需的空格,如果不超过MAXCOl则使用printLine
  • Incase of '\n',直接使用printLine并重新设置索引。
  • 如果索引为 10:
    • 使用 findBlank 找到最后一个空白并获得新索引。
    • 使用printLine打印当前行。
    • 使用 newIndex 函数获取新索引为 0 或新复制的 char 数组的索引。

代码

/* fold long lines after last non-blank char */

#include <stdio.h>

#define MAXCOL  10  /* maximum column of input */
#define TABSIZE 8   /* tab size */

char line[MAXCOL];  /* input line */

int expandTab(int index);
int findBlank(int index);
int newIndex(int index);
void printLine(int index);

void main() {
    int c, index;
    index = 0;
    while((c = getchar()) != EOF) {
        line[index] = c;    /* store current char */
        if (c == '\t')
            index = expandTab(index);
        else if (c == '\n') {
            printLine(index);   /* print current input line */
            index = 0;
        } else if (++index == MAXCOL) {
            index  = findBlank(index);
            printLine(index);
            index = newIndex(index);
        }
    }
}

/* expand tab into blanks */
int expandTab(int index) {
    line[index] = ' ';  /* tab is atleast one blank */
    for (++index; index < MAXCOL && index % TABSIZE != 0; ++index)
        line[index] = ' ';
    if (index > MAXCOL)
        return index;
    else {
        printLine(index);
        return 0;
    }
}

/* find last blank position */
int findBlank(int index) {
    while( index > 0 && line[index] != ' ')
        --index;
    if (index == 0)
        return MAXCOL;
    else
        return index - 1;
}

/* re-arrange line with new position */
int newIndex(int index) {
    int i, j;
    if (index <= 0 || index >= MAXCOL)
        return 0;
    else {
        i = 0;
        for (j = index; j < MAXCOL; ++j) {
            line[i] = line[j];
            ++i;
        }
        return i;
    }
}

/* print line until passed index */
void printLine(int index) {
    int i;
    for(i = 0; i < index; ++i)
        putchar(line[i]);
    if (index > 0)
        putchar('\n');
}