Relationship between integers and their names - Pr

2019-07-19 16:46发布

问题:

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.

回答1:

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...]



回答2:

I have an extravagant solution, which, nevertheless, could be practical for some situations.

Suppose the max number you plan to use is not too big, maybe a 1000 or 10000. And also you don't want to spend too much time creating Prolog program, don't want to deal with corner cases and are afraid of spelling errors (for example, because you are not a native English speaker, like me).

Then you can generate your Prolog program (list of facts) with another tool. Common Lisp has a standard tool to convert numbers to English words. Let's use it:

(defun generate-prolog (k)
  (loop for n from 1 to k do
   (format t "numnames([~a], ~a). ~%" 
           (substitute #\, #\Space 
                       (remove #\,
                               (substitute #\Space #\- (format nil "~R" n)) ) ) n) ) )
(generate-prolog 1000)

Fragment of the Prolog program generated with CLISP (other Lisp implementations can use American spelling, without 'and'):

numnames([two,hundred,and,thirty,six], 236). 
numnames([two,hundred,and,thirty,seven], 237). 
numnames([two,hundred,and,thirty,eight], 238). 
numnames([two,hundred,and,thirty,nine], 239). 
numnames([two,hundred,and,forty], 240). 
numnames([two,hundred,and,forty,one], 241). 
numnames([two,hundred,and,forty,two], 242). 
numnames([two,hundred,and,forty,three], 243). 
numnames([two,hundred,and,forty,four], 244). 
numnames([two,hundred,and,forty,five], 245). 
numnames([two,hundred,and,forty,six], 246). 
numnames([two,hundred,and,forty,seven], 247). 
numnames([two,hundred,and,forty,eight], 248). 
numnames([two,hundred,and,forty,nine], 249). 
numnames([two,hundred,and,fifty], 250). 


回答3:

I will report a well thought DCG solution proposed by Ken Johnson. Full code is here, with a small modification to make it usable (I tested in SWI-Prolog). I amended rules like ten_to_nine{*filter*}(13) --> [thir{*filter*}]. that sincerely I don't know how to handle.

:- module(dcg_numerals, [number//1]).

% Grammar for numbers, e.g.
% phrase(number(I),[two,hundred,and,fifty,six]).
% An astonishing characteristic of this code is that it's
% fully bidirectional. The expression
% phrase(number(256),Words)
% will instantiate Words = [two,hundred,and,fifty,six].
% What's more,
% phrase(number(I),Words)
% will eventually instantiate I and Words to all the numbers it knows.
%
% Ken Johnson 17-9-87

number(I) --> '1_to_999'(I).
number(I) --> '1000_to_999999'(I).

 '1_to_999'(I) --> '1_to_99'(I).
 '1_to_999'(I) --> '100_to_999'(I).

% Compound number with thousands

 '1000_to_999999'(I) --> thousands(I).

 '1000_to_999999'(I) --> thousands(K),'100_to_999'(Htu),
        {
                I is K + Htu
        }.

 '1000_to_999999'(I) --> thousands(K),[and],'1_to_99'(Tu),
        {
                I is K + Tu
        }.

% Thousands

thousands(I) --> '1_to_999'(K), [thousand],
        {
                I is K * 1000
        }.

% Compound number with hundreds, tens and units

 '100_to_999'(C) --> one_to_nine(H),[hundred],
        {
                C is 100 * H
        }.

 '100_to_999'(C) --> one_to_nine(H),[hundred],[and],'1_to_99'(Tu),
        {
                C is (100 * H) + Tu
        }.

% Complete number: a single word 1-9 or 10-19

 '1_to_99'(I) --> one_to_nine(I).
% '1_to_99'(I) --> ten_to_nine{*filter*}(I).
 '1_to_99'(I) --> ten_to_nine(I).
 '1_to_99'(I) --> multiple_of_ten(I).
 '1_to_99'(I) --> '20_to_99'(I).

% Compound number with tens and units

  '20_to_99'(I) --> multiple_of_ten(T), one_to_nine(U),
        {
                I is T + U
        }.

% Single words (terminal nodes)

one_to_nine(1) --> [one].
one_to_nine(2) --> [two].
one_to_nine(3) --> [three].
one_to_nine(4) --> [four].
one_to_nine(5) --> [five].
one_to_nine(6) --> [six].
one_to_nine(7) --> [seven].
one_to_nine(8) --> [eight].
one_to_nine(9) --> [nine].

/*
ten_to_nine{*filter*}(10) --> [ten].
ten_to_nine{*filter*}(11) --> [eleven].
ten_to_nine{*filter*}(12) --> [twelve].
ten_to_nine{*filter*}(13) --> [thir{*filter*}].
ten_to_nine{*filter*}(14) --> [four{*filter*}].
ten_to_nine{*filter*}(15) --> [fif{*filter*}].
ten_to_nine{*filter*}(16) --> [six{*filter*}].
ten_to_nine{*filter*}(17) --> [seven{*filter*}].
ten_to_nine{*filter*}(18) --> [eigh{*filter*}].
ten_to_nine{*filter*}(19) --> [nine{*filter*}].
*/
ten_to_nine(10) --> [ten].
ten_to_nine(11) --> [eleven].
ten_to_nine(12) --> [twelve].
ten_to_nine(13) --> [thirteen].
ten_to_nine(14) --> [fourteen].
ten_to_nine(15) --> [fifteen].
ten_to_nine(16) --> [sixteen].
ten_to_nine(17) --> [seventeen].
ten_to_nine(18) --> [eighteen].
ten_to_nine(19) --> [nineteen].

multiple_of_ten(20) --> [twenty].
multiple_of_ten(30) --> [thirty].
multiple_of_ten(40) --> [forty].
multiple_of_ten(50) --> [fifty].
multiple_of_ten(60) --> [sixty].
multiple_of_ten(70) --> [seventy].
multiple_of_ten(80) --> [eighty].
multiple_of_ten(90) --> [ninety].