Reputation: 199
Alright, so I'm working on a homework assignment, this is supposed to be a four function calculator taking in a list of strings [three, times, two] for example, and output a number. It only considers the numbers from one to twenty in its initial list. The following code is all my own. It runs up to the point where it takes in the last item in the list (that I've been using to test it, but the problem is for any of the inputs) in numberize and then will not unify.
calculator([twenty, times, three, plus, five, divided_by, two],Total).
I know the solution must be an easy one, but I'm not experienced enough yet in Prolog to figure it out.
My question is: how do I fix my code so that it runs the way I want it to?
calculator(X,Total):-
numberize(X,L),
reverse(L,L1),
func(L1,Total).
numberize([X,Y|T],L):-
str2num(X,X1),
numberize(T,[Y,X1|L]).
numberize([X],L):-
str2num(X,X1),
%somehow add on the X1 to the front of L without any errors and it's golden
/*Whatever that line is*/L is [X1|L].
func([X1,X,Z1|T], Total):-
(X == times, times(X1,Z1,Ttl));
(X == plus, plus(X1,Z1,Ttl));
(X == divided_by, divided_by(X1,Z1,Ttl));
(X == minus, minus(X1,Z1,Ttl)),
func([Ttl|T],Total).
str2num(one, X):- X is 1.
str2num(two, X):- X is 2.
str2num(three, X):- X is 3.
str2num(four, X):- X is 4.
str2num(five, X):- X is 5.
str2num(six, X):- X is 6.
str2num(seven, X):- X is 7.
str2num(eight, X):- X is 8.
str2num(nine, X):- X is 9.
str2num(ten, X):- X is 10.
str2num(eleven, X):- X is 11.
str2num(twelve, X):- X is 12.
str2num(thirteen, X):- X is 13.
str2num(fourteen, X):- X is 14.
str2num(fifteen, X):- X is 15.
str2num(sixteen, X):- X is 16.
str2num(seventeen, X):- X is 17.
str2num(eighteen, X):- X is 18.
str2num(nineteen, X):- X is 19.
str2num(twenty, X):- X is 20.
times(X,Y,Prod):-
Prod is X*Y.
plus(X,Y,Sum):-
Sum is X+Y.
divided_by(X,Y,Quo):-
Quo is X/Y.
minus(X,Y,Dif):-
Dif is X-Y.
Upvotes: 1
Views: 296
Reputation: 74197
The way I'd approach this is to start by defining a grammar for arithmetic expressions. The "standard" way of defining grammars is left-recursive. Since prolog does recursive descent parsing, the grammar can't be left-recursive. Every iteration has to remove something from the token stream, lest you go in to the death spiral of infinite recursion. Here's my non-left recursive grammar for a 4-banger calculator like yours:
expression : multiplicative_expression '+' expression
| multiplicative_expression '-' expression
| multiplicative_expression
;
multiplicative_expression : factor '*' multiplicative_expression
| factor '/' multiplicative_expression
| factor '%' multiplicative_expression
| factor
;
factor : '-' value
| '(' expression ')'
| value
;
value : number
Once we have the grammar, the prolog code pretty much writes itself. First, some facts to work with. We need a list of operators and their types (along with the equivalent prolog operator:
operator( plus , additive , '+' ) .
operator( minus , additive , '-' ) .
operator( times , multiplicative , '*' ) .
operator( divided_by , multiplicative , '/' ) .
operator( modulo , multiplicative , 'mod' ) .
And a words-to-numbers-map:
number_word( zero , 0 ).
number_word( one , 1 ).
...
number_word( nineteen , 19 ) .
number_word( twenty , 20 ) .
And we need our interface predicate, calculate/2
:
%--------------------------------------------------------------------
% we can calculate a result if Expr is a valid expression
% that consumes all the available tokens in the token stream
%---------------------------------------------------------------------
calculate(Expr,Result) :- expr( Expr , Result , [] ) .
That invokes the "start symbol" of the grammar, expr/3
. expr/3
(and the other worker predicates) are pretty much direct restatements of the grammar, with the additional requirement that they need to hand back the unconsumed portion of the input token stream. The parse is successful, if, at the end of the day, the token stream is empty:
expr( Xs , Result , Tail ) :- % per the grammar, an expression is
mult( Xs , LHS , [Sym|X1] ) , % - a multiplicative expression, followed by
operator( Sym , additive , Op ) , % - an infix additive operator, followed by
expr( X1 , RHS , X2 ) , % - another expression
Term =.. [Op,LHS,RHS] , % * in which case, we construct the proper prolog structure
Result is Term , % * in which case, we evaluate the result in the usual way
Tail = X2 % * and unify any remaining tokens with the Tail
. %
expr( Xs , Result , Tail ) :- % alternatively, an expression is simply
mult( Xs , Result , Tail ) % - a single multiplicative expression
. %
The worker predicate for multiplicative terms, mult/3
is pretty much identical — a direct restatement of the grammar:
mult( Xs , Result, Tail ) :- % a multiplicative expression is
factor( Xs , LHS , [Sym|X1] ) , % - a factor, followed by
operator( Sym , multiplicative , Op ) , % - an infix multiplicative operator, followed by
mult( X1 , RHS , X2 ) , % - another factor
evaluate( Op , LHS , RHS , Result ) , % * in which case, we evalute the result in the usual way
Tail = X2 % * and unify any remaining tokens with the tail
. %
mult( Xs , Result , Tail ) :- % alternatively, a multiplicative expression is simply
factor( Xs , Result , Tail ) % - a single factor
. %
Finally, since we're not wrassling with higher-precedence operations like unary minus, exponentiation or parentheses that change operator precedence, a factor is simply a number word that can be converted into an integer value:
factor( [X|Xs] , Value , Xs ) :- % a factor is simply
number_word(X,Value) % - a number value (in our case, a word that we convert to an integer)
.
and a simple helper to evaluate each subexpression as needed:
evaluate( Op , LHS , RHS , Result ) :- % to evaluate an infix term,
Term =.. [Op,LHS,RHS] , % - use univ to convert to the correct prolog structure, and
Result is Term % evaluate it as the result
. %
Upvotes: 2
Reputation: 7209
Small style remark: use facts for str2num/2
: just str2num(one, 1).
instead of str2num(one, X):- X is 1.
, etc. Added benefit is that now the predicate can be used both ways, like str2num(Word, 1)
.
As for the main question, you are almost correct.
The whole numberize
predicate can be as simple as this:
numberize([X], [N]) :-
str2num(X, N).
numberize([X, Op | T], [N, Op | NewT]) :-
str2num(X, N),
numberize(T, NewT).
Let's test it:
?- numberize([one, plus, two, minus, three], L).
L = [1, plus, 2, minus, 3]
But you need to remove call to reverse
from calculator
:
calculator(X,Total):-
numberize(X,L),
func(L,Total).
You have almost correct func
predicate. One problem: in Prolog you should have braces around disjunction:
func([X1,X,Z1|T], Total):-
(
X == times, times(X1,Z1,Ttl)
;
X == plus, plus(X1,Z1,Ttl)
;
X == divided_by, divided_by(X1,Z1,Ttl)
;
X == minus, minus(X1,Z1,Ttl)
),
func([Ttl|T],Total).
The second problem: when your list reduced to one number (think how func([1,plus,2], Total)
will call func([3], Total)
the predicate will fail. All you need to fix this is the rule that Total of a list with just 1 number is the number itself:
func([X], X).
Now the whole thing works:
?- calculator([one, plus, two], Total).
Total = 3
?- calculator([one, plus, two, minus, four], Total).
Total = -1
Upvotes: 2