首页 > 解决方案 > 是否可以在 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;
};

标签: cmathmathematical-expressions

解决方案


其他人建议使用函数指针,我同意这可能是正确的解决方案,但您确实要求提供一种向函数提供表达式的方法。当然,您可以这样做,但您必须自己实现表达式。

您可以为所需的各种子表达式创建一个结构,它可能如下所示:

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;
}

这段代码中很可能存在错误,我只是快速将其整理出来,但它应该让您了解如何使用表达式。


推荐阅读