【问题标题】:Folding input lines every nth column (K&R 1-22) in C在 C 中每第 n 列 (K&R 1-22) 折叠输入行
【发布时间】:2018-07-20 06:01:01
【问题描述】:

编写一个程序,在输入的第 n 列之前出现的最后一个非空白字符之后将长输入行“折叠”成两行或多行较短的行。确保你的程序用很长的行做一些智能的事情,如果在指定的列之前没有空格或制表符。

我决定遵循的算法如下:

  1. 如果输入行的长度
  2. 如果不是,我从 maxcol 向左检查,找到最接近的非空格字符是正确的,并将它们保存为“第一个”和“最后一个”。然后我打印从 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] = '\0';

    return i;
}

以下是问题:

  • 它不会用很长的线条做一些智能的事情(这很好,因为我可以将它添加为边缘情况)。
  • 它对选项卡没有任何作用。
  • 我无法理解输出的一部分。

例如,输入:

asd        de             def          deffff

我得到了输出:

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

问题 1 - 为什么会打印出意外的行?如何让我的程序/算法变得更好?

最终,在这个问题上花了很长时间后,我放弃了,决定查看clc-wiki 以获取解决方案。这里的每个程序都没有工作,保存一个(其他的没有工作,因为它们没有涵盖某些边缘情况)。有效的是最大的一个,对我来说没有任何意义。它没有任何 cmets,我也无法正确理解变量名及其代表的含义。但只有 wiki 中的 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 - 您能帮我理解这段代码及其工作原理吗?

它解决了我的解决方案的所有问题,并且还可以通过逐个字符读取,因此看起来更有效。

【问题讨论】:

  • 看到不需要的输出,我怀疑在某处缺少空终止,我只发现getline1 中写入了一个零。但要查明问题,我推荐ericlippert.com/2014/03/05/how-to-debug-small-programsstackoverflow.com/questions/2069367/how-to-debug-using-gdb,如果这些没有帮助ericlippert.com/2014/03/21/find-a-simpler-problem
  • @Yunnosch 感谢您提供这些链接! getline1 中的 '\0' 不算作空终止吗?
  • 这是我找到的唯一一个。我希望每个所需的输出线至少有一个。
  • 从 line[0:first] 打印后,我还使 line[0:len-last] = line[last:len]。那不应该将在 line[len] 处找到的 null 终止也复制到每个输出行吗?
  • 调试是否显示您描述的机制按预期工作?如果没有其他人愿意告诉您问题出在哪里,那么调试就是要走的路。调试时要遵循的提示:准确找出输出的哪一部分是由代码的哪一部分以及何时引起的。如果 "%s" 打印的内容多于预期,请确保存在 \0

标签: c arrays text-processing kernighan-and-ritchie


【解决方案1】:

问题 1 - 为什么会打印出意外的行?如何让我的程序/算法变得更好?

您在输出中得到了意外的行,因为在打印数组后,您没有用空字符 \0 终止新的 line 数组 -

这里你从last开始复制字符到len - 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] = '\0';

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

"de             def          deffff\n"

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

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

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

问题 2 - 你能帮我理解这段代码以及它是如何工作的吗?

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

【讨论】:

  • 谢谢!我添加了line [len - last] = '\0'; 行,现在一切正常。与其这样添加,不如把i &lt; len - last改成i &lt;= len - last,这样就连'\0'都被复制了?另外,我还没有真正被教过指针(还没有在书中),所以我不想使用它们。学习使用调试器非常有用。老实说,我以前从未使用过它。我要么用笔和纸,要么打印报表。我还没有开始使用调试器来找出第二个程序。我会的,然后将其作为单独的答案发布。
  • @ApoorvaAnand 由您决定如何在新数组末尾添加\0。我很欣赏你的态度。快乐学习。
【解决方案2】:

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

#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] != '\0'; ++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] = '\0';
    }
    prev_char = c;
  }
  printf("%s", buf);
  return 0;
}

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

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

【讨论】:

    【解决方案3】:

    以下是我的解决方案,我知道线程不再处于活动状态,但我的代码可能会帮助遇到问题的人掌握已经呈现的代码 sn-ps。

    *编辑

    解释

    • 继续读取输入,除非输入包含“\n”、“\t”或 至少 MAXCOl 个字符。
    • 如果是 '\t',使用 expandTab 替换为所需的空格,如果不超过 MAXCOl,则使用 printLine
    • 如果是'\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');
    }
    

    【讨论】:

    • @EmanuelP 我已经做出了改变!抱歉,我之前没有添加解释,因为我对 C 很陌生,对 SO 也很陌生!
    猜你喜欢
    • 1970-01-01
    • 2016-09-28
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多