c - 是否可以在 C 语言中给出数学表达式作为参数?
问题描述
我正在尝试构建一个函数,我可以将其作为参数提供一个数学表达式,例如“x**3+bx-c”,并通过牛顿方法返回给定函数的根。
我在想下面这个例子,其中'r0'是一个非零的初始随机数,dfunc是函数的导数。我不知道我将如何评估导数,但我想首先知道我是否可以将表达式作为参数做些什么。
double * roots (double r0, <type> func) {
double *r = alloc(100, sizeof(double));
r[0] = r0;
for (int i = 1; i <= 100; i++) {
r[i]=r[i-1]-func(r[i-1])/dfunc(r[i-1]);
};
return r;
};
解决方案
其他人建议使用函数指针,我同意这可能是正确的解决方案,但您确实要求提供一种向函数提供表达式的方法。当然,您可以这样做,但您必须自己实现表达式。
您可以为所需的各种子表达式创建一个结构,它可能如下所示:
struct expr
{
int refcount; // GC; exprs share sub-expressions
enum
{
CONS, VAR, MINUS, /* unary minus */
ADD, SUB, /* binary subtraction */
MUL, /* more... */
} type;
union {
struct { double x; } cons;
struct { int idx; /* index in var vector */ } var;
struct { struct expr *x; } unary;
struct { struct expr *x, *y; } binary;
};
};
我只是在这里坚持一些简单的。
在下面的派生代码中,我需要在乘法规则中保留表达式(如果你实现它,你需要对链式规则做同样的事情),所以我使用引用计数器处理内存管理。
struct expr *incref(struct expr *expr)
{
expr->refcount++;
return expr;
}
void free_expr(struct expr *);
struct expr *decref(struct expr *expr)
{
expr->refcount--;
if (expr->refcount > 1)
return expr;
free_expr(expr);
return 0;
}
我返回一个指向我修改的对象的指针,因为incref
. 我不使用它decref
,但我更喜欢保持这两个功能的接口一致。
创建表达式的实例没什么大不了的。您只需分配并设置其中的值。
struct expr *new_const(double x)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = CONS, .cons = {x}};
return expr;
}
struct expr *new_var(int idx)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = VAR, .var = {idx}};
return expr;
}
struct expr *new_minus(struct expr *x)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = MINUS, .unary = {x}};
return expr;
}
struct expr *new_add(struct expr *x, struct expr *y)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = ADD, .binary = {x, y}};
return expr;
}
struct expr *new_sub(struct expr *x, struct expr *y)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = SUB, .binary = {x, y}};
return expr;
}
struct expr *new_mul(struct expr *x, struct expr *y)
{
struct expr *expr = malloc(sizeof *expr);
assert(expr); // handle alloc errors
*expr = (struct expr){.refcount = 1, .type = MUL, .binary = {x, y}};
return expr;
}
对于适用于它们的功能,您可以使用switch
:
void print_expr(struct expr *expr)
{
switch (expr->type)
{
case CONS:
printf("%f", expr->cons.x);
break;
case VAR:
printf("x%d", expr->var.idx);
break;
case MINUS:
printf("-(");
print_expr(expr->unary.x);
printf(")");
break;
case ADD:
printf("(");
print_expr(expr->binary.x);
printf(" + ");
print_expr(expr->binary.y);
printf(")");
break;
case SUB:
printf("(");
print_expr(expr->binary.x);
printf(" - ");
print_expr(expr->binary.y);
printf(")");
break;
case MUL:
printf("(");
print_expr(expr->binary.x);
printf(" * ");
print_expr(expr->binary.y);
printf(")");
break;
/* more here ... */
}
}
使用函数指针,您可以实现多态性以避免切换,但这在这里可能是矫枉过正。
如果要计算表达式,则需要为变量提供值,一个简单的解决方案是只使用数组。这就是为什么上面的变量包含一个索引而不是一个字符串或类似的。索引被解释为变量值数组的偏移量。
double eval_expr(struct expr *expr, double *x)
{
switch (expr->type)
{
case CONS:
return expr->cons.x;
case VAR:
return x[expr->var.idx];
case MINUS:
return -eval_expr(expr->unary.x, x);
case ADD:
return eval_expr(expr->binary.x, x) + eval_expr(expr->binary.y, x);
case SUB:
return eval_expr(expr->binary.x, x) - eval_expr(expr->binary.y, x);
case MUL:
return eval_expr(expr->binary.x, x) * eval_expr(expr->binary.y, x);
/* more here ... */
}
}
为了计算导数,你根据推导规则实现转换规则(这就是为什么我在这里保持简单的表达式;我不想为链式规则或除法等编写代码;我是一个非常懒惰的人)。
struct expr *derivative(struct expr *expr, int var)
{
switch (expr->type)
{
case CONS:
return new_const(0);
case VAR:
return (expr->var.idx == var) ? new_const(1) : new_const(0);
case MINUS:
return new_minus(derivative(expr->unary.x, var));
case ADD:
return new_add(derivative(expr->binary.x, var), derivative(expr->binary.y, var));
case SUB:
return new_sub(derivative(expr->binary.x, var), derivative(expr->binary.y, var));
case MUL:
return new_add(new_mul(incref(expr->binary.x), derivative(expr->binary.y, var)),
new_mul(derivative(expr->binary.x, var), incref(expr->binary.y)));
/* more here ... */
}
}
我incref
在乘法规则中使用,所以我可以重用传入的表达式并且仍然安全地释放内存。释放一个表达式非常简单:
void free_expr(struct expr *expr)
{
switch (expr->type)
{
case CONS:
case VAR:
break;
case MINUS:
decref(expr->unary.x);
break;
case ADD:
case SUB:
case MUL:
decref(expr->binary.x);
decref(expr->binary.y);
break;
/* more here ... */
}
free(expr);
}
您可以像这样使用这些表达式:
int main(void)
{
struct expr *expr =
new_mul(
new_add(
new_const(3.5),
new_minus(new_var(0))),
new_sub(new_const(1.4),
new_var(1)));
print_expr(expr);
putchar('\n');
double x[] = {2.0, 42.0}; // variables
printf("value: %f\n", eval_expr(expr, x));
struct expr *deriv = derivative(expr, 0);
printf("derivative with respect to x0:\n");
print_expr(deriv);
putchar('\n');
printf("derivative value: %f\n", eval_expr(deriv, x));
free_expr(deriv);
deriv = derivative(expr, 1);
printf("derivative with respect to x1:\n");
print_expr(deriv);
putchar('\n');
printf("derivative value: %f\n", eval_expr(deriv, x));
free_expr(deriv);
free_expr(expr);
return 0;
}
这段代码中很可能存在错误,我只是快速将其整理出来,但它应该让您了解如何使用表达式。
推荐阅读
- python - pyinstaller 一档本地文件
- rust - 如何在 Bevy 中翻转精灵表
- python - 不和谐重写和 youtube 搜索的问题
- javascript - 按下某个键时触发两个链接之一
- javascript - 当异步身份验证登录功能出现新异常时,Express 服务器崩溃
- r - 替换给定R中另一个向量的负数位置的列?
- python - 减少机器学习的 Heroku Slug 大小(Python、PyTorch、Fastai)
- visual-studio - Hyperledger Iroha 构建问题
- python - 如何使用一系列键来索引值?
- python - Conda:如何取消由`anaconda`包指定的包?