我有以下问题,我有一个带有x项的字符串,有些则在括号中。我需要能够将括号中的术语分开,然后将它们放在列表的节点中:
列表中的第一个节点包含整个字符串,然后括号中的术语存储在以下节点中。
这样的东西:字符串:a*(b+(c+j)-h)*2 + (a+(b-c))
so,在列表中:firts节点:
a*(b+(c+j)-h)*2+(a+(b-c))->(b+(c+j)-h)->(c+j)->(a+(b-c))->(b-c)
我需要想法来实施术语分离的算法,我坚持这一部分。我已经实现了所有列表部分。他们要求我的功能是
void separate (char * s)
递归是一种解析嵌套结构的常见方法,正如@SomeProgrammerDude所说的那样。
我要寻找的算法的直觉是:
1(递归调用单独的方法。
2(每个呼叫都应在括号中包含一个术语,然后将其插入列表的末尾。
3(该方法必须达到两种模式:在输入字符串上迭代并在填写术语时通过输入字符串进行迭代。
我们正在寻找开放括号的第一次出现。一旦找到此方法,该方法就可以启用"读取"模式,并开始将其路径中的所有内容复制到本地缓冲区,直到找到闭合括号为止。
发生这种情况时,该方法禁用"读取"模式,标记该术语现在准备在列表中插入。
4(如何绕过嵌套术语?找到第一个开口括号后,保持括号的计数器,根据括号的数量进行调整(开口括号的 1,-1用于闭合(。
如果满足了开放括号,则计数器会增加1。如果计数器大于1,则指示我们发现了一个嵌套的术语,因此,当满足其关闭括号时,我们不应停止而是继续,同时将计数器减少1。
5(一旦插入了一个项,我们应该递归地调用我们的单独方法,现在输入字符串应该是已经解析的输入,从我们找到的第一个打开括号之后的字符开始。
。6(递归的基本案例?当所有输入被消耗时,我们到达了空字符串终结器。
完整代码示例(随着程序进行时显示状态的未点击打印语句(:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct listnode *Listptr;
struct listnode {
char* value;
Listptr next;
};
void insert_at_end(Listptr *, char *);
void print(Listptr);
void free_list(Listptr *);
void separate(Listptr *list, char *s)
{
if(!*s)
return;
int parentheses = 0, read = -1, i = 0;
char term[16] = {0}, *opening_parenthesis;
//printf("Before while input is %sn", s);
while(*s)
{
//printf("#parentheses = %d, read = %d, i = %d, term = %sn", parentheses, read, i, term);
if(*s == '(')
{
if(parentheses == 0)
opening_parenthesis = s;
read = 1;
parentheses++;
}
else if(*s == ')')
{
if(parentheses > 1)
parentheses--;
else if(parentheses == 1)
{
term[i++] = *s;
read = 0;
}
}
if(read == 1)
{
term[i++] = *s;
//puts(term);
}
else if (read == 0)
{
insert_at_end(list, term);
separate(list, opening_parenthesis + 1);
return;
}
s++;
}
}
int main(void)
{
char buf[128];
fgets(buf, sizeof(buf), stdin);
// eat trailing newline of fgets
if(!strlen(buf))
{
printf("No input, exiting..n");
return EXIT_FAILURE;
}
// eat trailing newline of fgets
buf[strlen(buf)] = ' ';
// Goal: a*(b+(c+j)-h)*2+(a+(b-c))->(b+(c+j)-h)->(c+j)->(a+(b-c))->(b-c)
Listptr list = NULL;
insert_at_end(&list, buf);
//printf("First node: "); print(list);
separate(&list, buf);
print(list);
free_list(&list);
return EXIT_SUCCESS;
}
void insert_at_end(Listptr *ptraddr, char* str)
{ /* Insert str as last element of list *ptraddr */
while (*ptraddr != NULL) /* Go to end of list */
ptraddr = &((*ptraddr)->next);/* Prepare what we need to change */
/* In real application, check for NULL pointer of malloc! */
*ptraddr = malloc(sizeof(struct listnode)); /* Space for new node */
/* Space for string. Length of str + 1 for the null terminator */
(*ptraddr)->value = malloc((strlen(str) + 1) * sizeof(char));
strcpy((*ptraddr)->value, str); /* Copy str */
(*ptraddr)->next = NULL; /* There is no next element */
}
void print(Listptr list) /* Print elements of list */
{
while (list != NULL) { /* Visit list elements up to the end */
printf("%s--> ", list->value); /* Print current element */
list = list->next; /* Go to next element */
}
printf("NULLn"); /* Print end of list */
}
void free_list(Listptr *ptraddr)
{
Listptr templist = *ptraddr;
while(*ptraddr != NULL) {
*ptraddr = (*ptraddr)->next;
free(templist->value);
free(templist);
templist = *ptraddr;
}
*ptraddr = NULL;
}
输出(输入(:
a*(b (c j(-h(*2 (a (b-c(( ->(b (c j(-h( ->(c j( ->(a (b-c(( ->(b-c( -> null
Note1:我更改了您单独的方法的原型,以允许将列表作为参数传递。我想在您的实施中,列表是一个全局变量之类的东西,通常应该避免。
note2:仅使用括号数量的变量来确定何时读取(何时呈阳性(,就可以在不使用读标志的情况下实现它,您可以分配保留值(-1例如(,指定何时完成阅读,并且该术语已准备就绪列表插入。
假设1:术语的长度最多是15个字符。代码通常应检查该长度是否没有超过 - 在我的实现中未完成。
ashaption2:假定术语有效,即它包含相同数量的开口和闭合括号。在现实世界中,应该对输入进行消毒。
live demo
ps:我也在strlist(c(中实现列表。
John Bollinger推荐:
大概是为此目的的递归功能将返回有关解析了多少字符串的信息,以便其呼叫者可以在字符串的其余部分(如果有(中捡起。这意味着它不一定是递归的((本身是单独的((本身,而是它调用的某些辅助功能。
op已经绘制了草图(在评论中(:
如果给出一个函数,它将返回一个带有两个括号内部的子字符串,我可以将其插入列表中。然后,我可以按照他们上面告诉我的递归来回电。
这是我开始的地方。
有多种构建子字符串的方法:
-
通过用定界符
' '
覆盖字符(可能是暂时(&ndash;就像完成在strtok()
中 为子字符串分配内存,并复制原始字符串的部分(使用例如
strdup()
(只需返回子弦的长度(并且可能是,开始(
我使用了第三个选项。
#include <assert.h>
#include <stdio.h>
#include <string.h>
void add_term(char *s, size_t len)
{
printf("'%.*s'n", (int)len, s);
}
size_t find_term(char *s, size_t len)
{
assert(*s == '(');
int n = 1;
size_t i = 1;
for (; i < len && n; ++i) n += (s[i] == '(') - (s[i] == ')');
return n ? fprintf(stderr, "ERROR!n"), 0 : i;
}
void separate_terms(char *s, size_t lenTotal, int addTotal)
{
for (size_t i = 0; i < lenTotal;) {
switch (s[i]) {
case '(': {
const size_t len = find_term(s + i, lenTotal - i);
if (!len) return;
if (len < lenTotal + addTotal) add_term(s + i, len);
separate_terms(s + i + 1, len - 2, 1);
i += len;
} break;
case ')': fprintf(stderr, "ERROR!n"); return;
default: ++i;
}
}
}
void separate(char *s)
{
size_t lenTotal = strlen(s);
printf("Term: "); add_term(s, lenTotal);
separate_terms(s, lenTotal, 0);
}
int main(void)
{
// sample of OP:
separate("a*(b+(c+j)-h)*2 + (a+(b-c))");
}
输出:
Term: 'a*(b+(c+j)-h)*2 + (a+(b-c))'
'(b+(c+j)-h)'
'(c+j)'
'(a+(b-c))'
'(b-c)'
注意:
op指出,已经解决了列表中的结果存储(具有适当的管理(。因此,我改用简单的控制台输出(在add_term()
中(。
find_term()
只是在计算开口和闭合括号,直到找到与第一个开口相对应的闭合括号(成功(或达到字符串末端(错误情况(。
separate_terms()
是递归的下降扫描仪,以在一项术语中查找子字体。
它迭代输入字符串的字符。每次找到开口括号((
(时,都会调用find_term()
来确定子术语的长度并添加结果。通过递归地将自己自称为限制的长度递归地分析。之后,由于现在对其进行了完全分析,因此跳过了子术语的长度。
在摆弄边缘案例时,我发现我的初始版本(明显较短(的版本报告了当完整的输入项在括号中(例如(a + b)
(时重复的项。因此,我添加了一个标志addTotal
来表示如果输入总长度,是否应添加发现的子术语。
separate()
很短。这只是一个包装器,添加一个术语以进行完整输入并执行递归separate_terms()
的高级调用。(0
用于addTotal
,以防止重复添加完整输入。(
我制作了其他一些样本,我认为这些样本是我的解决方案的边缘案例,以检查潜在的错误:
// valid edge cases:
separate(""); // valid?
separate("()"); // valid?
separate("a");
separate("(a + b)");
separate("(a + b) * 2");
separate("(a + b) * (c - d)");
separate("((a + b))");
// invalid cases:
separate("(");
separate("((");
separate(")");
separate("))");
separate("())(()");
separate("a(a(a");
separate("((a)))");
输出:
Term: ''
Term: '()'
Term: 'a'
Term: '(a + b)'
Term: '(a + b) * 2'
'(a + b)'
Term: '(a + b) * (c - d)'
'(a + b)'
'(c - d)'
Term: '((a + b))'
'(a + b)'
Term: '('
ERROR!
Term: '(('
ERROR!
Term: ')'
ERROR!
Term: '))'
ERROR!
Term: '())(()'
'()'
ERROR!
Term: 'a(a(a'
ERROR!
Term: '((a)))'
'((a))'
'(a)'
ERROR!
coliru