字符串的展开
这是NOIP 2007提高组复赛的第2题,考察的是字符串的处理技术。题目详情和在线测试地址,在这里。
本题的特点,是字符串应当如何被展开的规则较为复杂。如何使自己的程序清楚地执行这些规则,是对程序员的挑战。
我花了一些时间,为这道题写代码。最终我写的代码应用了包括main()在内的7个函数,他们之间的相互调用关系如下:
如果仔细观察其中的代码,你会发现:每个函数所用的代码行都很短,逻辑结构都很简单。
比如main(),是这样的:主要就是在扫描输入字符串的过程中,回答一个问题:遇到'-'要不要展开?
在判断“要不要展开”的逻辑上,比较复杂,所以,我又写了一个函数shouldBeExpanded(),这个函数的结果,就是回答“要不要展开'-'”这个问题的。
shouldBeExpanded()的逻辑,也很简单:(10行代码:44~54)
对于我们需要展开的'-',其面对的局面就比较复杂了。因为有p1,p2,p3,共3个参数控制着如何展开'-'。如何处理这3个参数,以及按照怎样的顺序来处理这3个参数,成为考验程序员智慧的关键。
我的策略是:先考虑p3,它控制着展开字符序列的顺序。仔细观察expand(),你会发现,它只针对p3,进行了不同形式的展开。顺序展开的时候,用的是printCharSequenceAsc(),逆序展开的时候,用的是printCharSequenceDesc()。这两个函数的应用,使得expand()本身的代码逻辑异常简单:(13行代码)如下所示,注意:只对p3进行了判断。
对于顺序展开的情况,printCharSequenceAsc()的代码更是极其简单:(7行代码)
对于逆序展开的情况,printCharSequenceDesc()也是极其简单的7行代码:
在展开'-'的过程中,还要考虑展示的是什么字符,这是由p1参数决定的,共有3种可能:小写字母、大写字母、'*'。所以printCharSequenceAsc()和printCharSequenceDesc()都会呼叫repeatChar()来展示合适的字符。
repeatChar()的逻辑结构也很简单:15行代码
在我们上述分析的7个函数中,除了main()因为有空白行,输入数据部分在内,有25行代码。其他的函数,都只有十来行代码,甚至只有7行代码。
通过这道题目,我们认识到,将一个复杂的问题,分解成若干个小问题,用一个个小小的函数,逐个处理这些小问题,最终解决完整的大问题。这是我们在程序设计中要懂得的一种思维方式:分而治之。
巨大的挑战在于:
- 大问题如何分解成小问题:分成几个小问题?如何精确界定每个小问题所涵盖的范围?
- 如何设定各个函数之间呼叫时所需要传递的参数?
最后,我们还得到的一个经验就是:结构良好的函数关系,将使得某些函数被反复使用:repeatChar()。
设计结构良好的函数间关系,有助于提高代码的质量。本代码在在线测试中,一次性通过所有测试数据。
为了方便阅读,下面给出完整的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
/* Name: expand.c Copyright: All rights reserved. Author: fzd19zx@gmail.com Date: 24-01-12 17:21 Description: NOIP2007年提高组第2道 http://tyvj.cpwz.cn/Problem_Show.asp?id=1053 */ # include "stdio.h" # define MAX_LENGTH (101) /*字符串的长度上限*/ char st[MAX_LENGTH]; /*需要处理的字符串*/ int p1, /*决定填充的内容*/ p2, /*填充字符的个数*/ p3; /*填充字符的顺序*/ /* 功能:检查输入的两个字符参数:x,y是否同属于小写字母或数字。 返回: 0:x,y不同属于小写字母或数字; 1:x,y同属于小写字母或数字; */ int isSameType(char x, char y) { if ( ('a'<=x && x<='z') && ('a'<=y && y<='z') /*x,y同为小写字母*/ || ('0'<=x && x<='9') && ('0'<=y && y<='9') /*x,y同为数字*/ ) return 1; else return 0; } /* 功能:检查st[]中,下标为i的'-'字符(st[i]为'-'),是否需要被展开? 这部分功能的实现,必须严格按照题目的意思来描述。 返回: 0:这个'-'不需要被展开; 1:这个'-'需要被展开。 */ int shouldBeExpanded(char st[], int i) { if ( (i>=1 && i<=strlen(st)-2) /*'-'的位置,不应该位于st[]的开始和末尾*/ && isSameType(st[i-1],st[i+1]) /*'-'左右两侧的字符同属于小写字母或数字*/ && st[i-1]<st[i+1] /*'-'右侧的字符严格大于左侧的字符*/ ) return 1; else return 0; } /* 功能:根据p1的值显示字符,而且每个字符重复p2次 */ void repeatChar(char c, int p1, int p2) { int i; if ( p1==2 && 'a'<=c && c<='z' ) c=c+'A'-'a'; if (p1==3) c='*'; for (i=1; i<=p2; i=i+1) { printf("%c",c); } return; } /* 功能:从字符(x+1)一路顺序打印到(y-1),根据p1的值显示字符,而且每个字符重复p2次 */ void printCharSequenceAsc(char x, char y, int p1, int p2) { char i; for (i=x+1; i<=y-1; i=i+1) { repeatChar(i,p1,p2); } return; } /* 功能:从字符(x+1)一路逆序打印到(y-1),根据p1的值显示字符,而且每个字符重复p2次 */ void printCharSequenceDesc(char x, char y, int p1, int p2) { char i; for (i=y-1; i>=x+1; i=i-1) { repeatChar(i,p1,p2); } return; } /* 功能:根据i的位置,以及参数p1,p2,p3的指示,展开'-'(即把扩展出来的字符统统打印出来) 约定:该函数不判断'-'是否需要展开;也不判断i的合法/合理性。 */ void expand(char st[], int i, int p1, int p2, int p3) { char c; int j; if (p3==1) { /*按照p1,p2的指示,顺序展开'-' */ printCharSequenceAsc(st[i-1],st[i+1],p1,p2); } else { /*按照p1,p2的指示,逆序展开'-' */ printCharSequenceDesc(st[i-1],st[i+1],p1,p2); } return; } int main() { int i; freopen("expand.in.txt","r",stdin); freopen("expand.out.txt","w",stdout); /*读入字符串st[], p1, p2, p3*/ scanf("%d%d%d",&p1,&p2,&p3); scanf("%s",st); /*从左到右,扫描st中的每一个字符*/ for (i=0; i<strlen(st); i=i+1) { if (st[i]!='-') printf("%c",st[i]); else { /*如果st[i]不需要展开,则输出str[i]本身*/ if (!shouldBeExpanded(st,i)) printf("%c",st[i]); else { /*否则的话,根据i的位置,以及参数p1,p2,p3的指示,展开'-'*/ expand(st,i,p1,p2,p3); } } } return 0; } |