Search code examples
numbersprologdcg

Relationship between integers and their names - Prolog


I'm trying to explore the concepts of natural numbers, Peano numbers, arithmetic, etc. in Prolog. Right now I'm trying to create a predicate that will allow me to enter the name of any number and get it's numerical value (or vice versa). How would I go about this? My idea was that I would translate the numbers given and them add them together using the plus function (e.g. one hundred and forty-five: one, hundred = 100, and = 0, forty = 40, five = 5 -> 100 + 0 + 40 + 5 = 145.

Here's an example of some queries:

?- numnames(X, 375).
X = [three, hundred, and, seventy, five] ;
false.
?- numnames([seven], 7).
true ;
false.

Here are some of my exhaustive facts (I just picked some that would address a certain category):

numnames([],0).
numnames(and,0).
numnames([one],1).
numnames([ninety],90).
numnames([one, hundred], 100).

I'm just confused as to how to translate the numbers before the arithmetic, and also where/when do I stop making exhaustive facts and start making rules? Thanks for the help.


Solution

  • This is a nice application for Prolog grammar rules or DCGs (basically syntactic sugar that hides some list manipulation and has a relatively straightforward translation to normal Prolog rules).

    num_0_999(0) --> [zero].
    num_0_999(N) --> num_1_99(N).
    num_0_999(N) --> num_100_999(N).
    
    num_1_99(N) --> num_1_9(N).
    num_1_99(N) --> num_10_19(N).
    num_1_99(N) --> decade(T), opt_1_9(U), {N is T+U}.
    
    num_100_999(N) --> num_1_9(H), [hundred], opt_1_99(S), {N is 100*H+S}.
    
    opt_1_9(0) --> [].
    opt_1_9(N) --> num_1_9(N).
    
    opt_1_99(0) --> [].
    opt_1_99(N) --> [and], num_1_99(N).
    
    num_1_9(1) --> [one].   num_1_9(2) --> [two].   num_1_9(3) --> [three].
    num_1_9(4) --> [four].  num_1_9(5) --> [five].  num_1_9(6) --> [six].
    num_1_9(7) --> [seven]. num_1_9(8) --> [eight]. num_1_9(9) --> [nine].
    
    num_10_19(10) --> [ten].        num_10_19(11) --> [eleven].
    num_10_19(12) --> [twelve].     num_10_19(13) --> [thirteen].
    num_10_19(14) --> [fourteen].   num_10_19(15) --> [fifteen].
    num_10_19(16) --> [sixteen].    num_10_19(17) --> [seventeen].
    num_10_19(18) --> [eighteen].   num_10_19(19) --> [nineteen].
    
    decade(20) --> [twenty].        decade(30) --> [thirty].
    decade(40) --> [forty].         decade(50) --> [fifty].
    decade(60) --> [sixty].         decade(70) --> [seventy].
    decade(80) --> [eighty].        decade(90) --> [ninety].
    

    This work both ways (and can enumerate all the numbers):

    ?- phrase(num_0_999(46), Name).
    Name = [forty, six]
    Yes (0.00s cpu, solution 1, maybe more)
    
    ?- phrase(num_0_999(N), [forty, six]).
    N = 46
    Yes (0.00s cpu, solution 1, maybe more)
    

    [I had originally used a #=/2 constraint instead of is/2 to make the code work in both modes, but was reminded by @CapelliC's post that the same can be achieved in plain Prolog by moving the arithmetic to the end of the respective rules...]