【问题标题】:Generate all expressions from list of numbers equal to a number [PROLOG]从等于数字的数字列表中生成所有表达式 [PROLOG]
【发布时间】:2019-08-04 09:23:05
【问题描述】:

我得到一个数字列表,例如[22,45,2,6,7,...]

现在我必须在数字之间插入二元运算符:+-/* 和括号(),以便表达式等于给定数字k

列出所有可能的表达式,这些表达式由插入运算符和括号创建,它们将给出 k 的总和。

结果表达式中数字的位置必须固定,即仅在数字之间或数字周围插入运算符和括号

例如:给定数字k=9 和列表[1,2,3],一种解决方案是[(,(,1,+,2,),*,3,)]。 我该怎么做?

[我目前的错误解决方案]: 现在我知道如何评估像[1,+,3,*,5] 这样的表达式,从左到右吃 Operand1,Operator,Operand2 直到没有东西吃。

但我也必须插入括号..

任何人都可以草拟解决方案或给出提示吗?

这是一道古老的考试题,我正在准备 3 个月后的考试,所以我正在尝试解决这些问题,但我被卡住了。

编辑:这是序言问题。

【问题讨论】:

  • 以后请少用粗体字。如果你用很多粗体字也没有用,因为人们不再知道去哪里看。
  • 此外,必须固定数字的顺序是什么意思?
  • 您只需要适当地插入运算符和括号,您不能将数字列表从 [1,2,3] 更改为 [2,1,3] - 我猜这来自“仅插入” " 但我再次明确指出。
  • 第一个提示是,在考虑 Prolog 之前,您需要定义您认为的规则是什么。如果你可以用普通语言定义你的规则,那么 Prolog 就可以遵循。特别是关于括号,您认为何时插入左括号或右括号是一个有效点?例如,您将在数字前插入左括号,并且必须在其前面有运算符、左括号或什么都没有。您可以在数字等之后插入右括号。并且需要平衡括号。想想规则,先用自然语言写下来。
  • going from left to right and eating...until there is nothing to eat 似乎是错误的评估策略。您应该考虑运算符的优先级。

标签: list prolog expression binary-tree


【解决方案1】:

我认为在遍历输入时尝试直接用括号构建结果列表是一个坏主意。更容易构建表达式的语法树,其叶子用给定列表的元素标记,然后在单独的步骤中处理它。

例如:

?- leaves_expr([A,B,C,D], Expr).
Expr = leaf(A)+ (leaf(B)+ (leaf(C)+leaf(D))) ;
Expr = leaf(A)+ (leaf(B)+leaf(C)*leaf(D)) ;
Expr = leaf(A)+ (leaf(B)+leaf(C)+leaf(D)) ;
Expr = leaf(A)+ (leaf(B)*leaf(C)+leaf(D)) ;
Expr = leaf(A)+leaf(B)* (leaf(C)+leaf(D)) ;
Expr = leaf(A)+leaf(B)* (leaf(C)*leaf(D)) ;

这可以如下实现:

leaves_expr([X], leaf(X)).
leaves_expr(Leaves, X + Y) :-
    append([L|Left], [R|Right], Leaves),
    leaves_expr([L|Left], X),
    leaves_expr([R|Right], Y).
leaves_expr(Leaves, X * Y) :-
    append([L|Left], [R|Right], Leaves),
    leaves_expr([L|Left], X),
    leaves_expr([R|Right], Y).

append/3 调用用于将叶子列表分解为非空 部分以避免非终止问题。 我会对使用 DCG 以优雅的方式进行此操作感兴趣。

然后,给定这样的表达式树,我们可以再次以完全括号形式“输出”它:

expr_parenthesized(leaf(X)) -->
    [X].
expr_parenthesized(X + Y) -->
    ['('],
    expr_parenthesized(X),
    [+],
    expr_parenthesized(Y),
    [')'].
expr_parenthesized(X * Y) -->
    ['('],
    expr_parenthesized(X),
    [*],
    expr_parenthesized(Y),
    [')'].

将这两部分组合起来,我们得到:

?- leaves_expr([A,B,C], Expr), expr_parenthesized(Expr, Parenthesized).
Expr = leaf(A)+ (leaf(B)+leaf(C)),
Parenthesized = ['(', A, +, '(', B, +, C, ')', ')'] ;
Expr = leaf(A)+leaf(B)*leaf(C),
Parenthesized = ['(', A, +, '(', B, *, C, ')', ')'] ;
Expr = leaf(A)+leaf(B)+leaf(C),
Parenthesized = ['(', '(', A, +, B, ')', +, C, ')'] ;
Expr = leaf(A)*leaf(B)+leaf(C),
Parenthesized = ['(', '(', A, *, B, ')', +, C, ')'] ;
Expr = leaf(A)* (leaf(B)+leaf(C)),
Parenthesized = ['(', A, *, '(', B, +, C, ')', ')'] ;
Expr = leaf(A)* (leaf(B)*leaf(C)),
Parenthesized = ['(', A, *, '(', B, *, C, ')', ')'] ;

等等。如果您编写简单的谓词 expr_value/2 来评估此类表达式(由叶子上的数字构成),那么您就完成了。

【讨论】:

    【解决方案2】:

    考虑括号问题而不实际放置任何括号的一种方法是使用后缀表示法。换句话说:

    (a + b) * c
    

    变成:

    a b + c *
    

    这是规范 Prolog 表示法中的以下树:

    *(+(a, b), c)
    

    同样:

    a + (b * c) ---> a b c * + ---> +(a, *(b, c))
    

    举个完整的例子,有三个操作数,1、2 和 3,并且只有 +* 作为运算符,为了简短起见,您会得到:

    1 2 + 3 + ---> (1 + 2) + 3 = 6
    1 2 + 3 * ---> (1 + 2) * 3 = 9
    1 2 * 3 + ---> (1 * 2) + 3 = 6
    1 2 * 3 * ---> (1 * 2) * 3 = 6
    1 2 3 + + ---> 1 + (2 + 3) = 6
    1 2 3 + * ---> 1 * (2 + 3) = 5
    1 2 3 * + ---> 1 + (2 * 3) = 7
    1 2 3 * * ---> 1 * (2 * 3) = 6
    

    查看第一列,我得到以下一般概念:您从 n 操作数和 n-1 二元运算符开始。您将前两个操作数压入堆栈,并且需要再执行 2*n-3 步。在每一步,您要么推送一个操作数,要么应用一个运算符。如果你还有剩余,你总是可以压入一个操作数。仅当堆栈上有两个或多个操作数时才能应用运算符;那时您将不得不减少堆栈。

    回溯将负责枚举所有可能性(因此这是对解决方案空间的典型暴力穷举搜索)。您将有两个选择点来源:选择一个运算符;以及推动或减少。

    考虑到这一点,我得出了一个谓词的以下实现,它接受一个操作数列表、一个二元运算符列表,并为您提供一个“带括号”的表达式:

    expr(Operands, Operators, E) :-
        Operands = [A, B|Rest],
        length(Operands, N),
        Steps is 2*N - 3,
        expr(Steps, Rest, [B, A], Operators, E).
    

    这会将前两个操作数压入堆栈并计算剩余步数。

    expr(Steps, Operands, Stack, Operators, E) :-
        (   succ(Steps0, Steps) ->
            next(Steps0, Operands, Stack, Operators, E)
        ;   Stack = [E]
        ).
    

    这里我用succ/2倒数到0然后停止;最后,堆栈中唯一的元素就是您的表达式。

    next(Steps, Operands, Stack, Operators, E) :-
        push(Operands, Stack, Operands_next, Stack_next),
        expr(Steps, Operands_next, Stack_next, Operators, E).
    next(Steps, Operands, Stack, Operators, E) :-
        member(Op, Operators),
        reduce(Stack, Op, Stack_next),
        expr(Steps, Operands, Stack_next, Operators, E).
    

    这是您推动或减少的地方。两个单独的子句是选择点的第一来源;使用member/2 从列表中获取一个运算符是另一个。

    push([X|Xs], S0, Xs, [X|S0]).
    
    reduce([A,B|Stack], Op, [X|Stack]) :-
        X =.. [Op, B, A].
    

    实现推送和缩减是微不足道的。我使用“univ”运算符=..[+, 1, 2] 之类的列表中生成+(1, 2) 之类的术语。

    有了这个,你已经可以问“我如何使用 +、* 和括号将 [1,2,3] 变成 7”:

    ?- expr([1,2,3], [+,*], E), E =:= 7.
    E = 1+2*3 ;
    false.
    

    这是最基本的“生成和测试”:生成算术表达式,然后测试它们是否计算为一个值。如果省略测试,则可以看到所有表达式:

    ?- expr([1,2,3], [+,*], E).
    E = 1+(2+3) ;
    E = 1*(2+3) ;
    E = 1+2*3 ;
    E = 1*(2*3) ;
    E = 1+2+3 ;
    E =  (1+2)*3 ;
    E = 1*2+3 ;
    E = 1*2*3 ;
    false.
    

    一个奇怪的细节是,因为+* 已经被定义为中缀运算符,Prolog 会为您编写它们,甚至为它们加上括号。我不知道像E = (1+2)*3 这样的解决方案对您来说是否足够好,或者您真的需要['(', 1, +, 2, ')', *, 3]The other answer 似乎已经有一个可行的解决方案。因为这里的表达式已经是一个有效的算术表达式,你必须稍微调整一下。我可能会这样写:

    infix(N) -->
        {   number(N)
        }, !,
        [N].
    infix(E) -->
        {   compound(E),
            E =.. [Op, A, B]
        }, !,
        ['('], infix(A), [Op], infix(B), [')'].
    

    我也不知道 1+2+3 = 3+3 = 6 是否和 1+(2+3) = 1+5 = 6 一样:需要考虑关联性吗?

    无论哪种方式,您都可以将expr/3 包装在这样的谓词中:

    equals_k(Numbers, K, E) :-
        expr(Numbers, [+,-,*,/], E0),
        K =:= E0,
        phrase(infix(E0), E).
    

    PS:除以零异常很容易,例如:

    ?- expr([1,0], [/], E), R is E.
    

    【讨论】:

      【解决方案3】:

      这是我的解决方案建议,我发现它简单明了, 复制并粘贴到记事本++ 编辑器以获得最佳可读性。

      * ________________________________________________                           *
      *|find_expression(NumsList,TargetValue,Expression)|                          *
      **------------------------------------------------*                          *
      * Expression is an arithmetic expression of the numbers in Numslist with     *
      * possible operators '+','-','*','/' and '(' and ')' between the numbers     *
      *  in such a way that the expression evaluates to the TargetValue argument   *  
      *****************************************************************************/%
      
      /* a single element number list can evaluate only to itself */ 
      find_expression([SingleNumber],SingleNumber,SingleNumber).
      
      /* expression of a multypile number list */ 
      find_expression(NumberList,Target,Expression):-
      
          /* non-deterministically  divide the number list 
           into 2 separate lists which include at least one number each*/ 
          append([X|Xs],[Y|Ys], NumberList),
      
          /* recursively find an expression for east list, 
             where the expression evaluates to itself */ 
          find_expression([X|Xs],Exp1,Exp1),
          find_expression([Y|Ys],Exp2,Exp2),
      
          /* non-deterministically  choose an operand from [+,-,*,division] 
             and compose Expression to be (Exp1 Operand Exp2) */ 
          (   member(Expression,[Exp1+Exp2,Exp1-Exp2,Exp1*Exp2]) 
              ; /* prevent zero divison */
              (Val2 is Exp2, Val2 =\= 0, Expression = (Exp1/Exp2))), %/*
      
          /* assure that final expression evaluates(matches) the targe value 
             and convert value from integer to float if necessary */
          ( Target = Expression ; Target is Expression 
            ; FloatTarget is Target*1.0, FloatTarget is Expression)
      

      【讨论】:

        猜你喜欢
        • 2012-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-28
        • 2021-12-25
        • 2017-11-15
        • 2021-12-04
        相关资源
        最近更新 更多