Prolog将整数转换为数字列表

5

我想编写一个谓词,接受一个整数和一个数字列表作为参数,并在Digits中包含整数的数字并按正确的顺序排列时成功,即:

?-digit_lists( Num, [1,2,3,4] ).
[Num == 1234].

这是我目前的进展:

my_digits( 0, [] ).
my_digits(N,[A|As]) :- N1 is floor(N/10), A is N mod 10, my_digits(N1, As).

你有尝试过什么吗?在没有尝试任何东西之前就寻求作业帮助是不好的行为。 - Daniel Lyons
在调用 my_digits/2 之前尝试反转列表,因为这样你的逻辑会更加适用。 - ssbarbee
6个回答

5
我认为这更容易理解:
numToList(NUM,[LIST|[]]):-
   NUM < 10,
   LIST is NUM,
   !.
numToList(NUM,LIST):-
   P is NUM // 10,
   numToList(P,LIST1),
   END is (NUM mod 10), 
   append(LIST1,[END] ,LIST).

尽管我喜欢这种方法的简单性,但需要注意的是,它不幸的是不能双向工作(给出一个列表将无法构建数字),这是我们在Prolog中应该努力实现的目标。 - Gabriel Jablonski

4

正如已经建议的那样,请考虑使用有限域约束:

:- use_module(library(clpfd)).

number_digits(Number, 0, [Number]) :- Number in 0..9.
number_digits(Number, N, [Digit|Digits]) :-
        Digit in 0..9,
        N #= N1 + 1,
        Number #= Digit*10^N + Number1,
        Number1 #>= 0,
        N #> 0,
        number_digits(Number1, N1, Digits).

这个谓词可以在所有方向上使用。以下是已实例化任一参数的示例:

?- number_digits(215, _, Ds).
Ds = [2, 1, 5] ;
false.

?- number_digits(N, _, [4,3,2,1]).
N = 4321 ;
false.

还有两个常见问题:

?- number_digits(N, _, [A,B]).
N in 10..99,
_G2018+B#=N,
_G2018 in 10..90,
A*10#=_G2018,
A in 0..9,
B in 0..9 ;
false.

?- number_digits(N, _, Ds).
Ds = [N],
N in 0..9 ;
Ds = [_G843, _G846],
N in 0..99,
_G870+_G846#=N,
_G870 in 0..90,
_G843*10#=_G870,
_G843 in 0..9,
_G846 in 0..9 ;
etc.

2
“Digit in 1..9” 是打字错误吗?我原本期望的是 “Digit in 0..9”。 - Wouter Beek
1
是的,那是个打错字。谢谢! - mat

2
这是一个基于的变体... 基于(#=)/3if_//3,我们定义:

n_base_digits(N, R, Ds) :-
   N #> 0,                                  % 只考虑正整数
   R #> 1,                                  % 最小进制为2
   Ds = [D|_],                              % 最高位不能为0
   D #> 0,
   phrase(n_base_digits_aux(N, R, Ds), Ds).
n_base_digits_aux(N, Base, [_|Rs]) --> { D #= N mod Base, M #= N // Base }, if_(M #= 0, { Rs = [] }, n_base_digits_aux(M, Base, Rs)), [D].

在SICStus Prolog 4.3.3中查询:

| ?- n_base_digits(1234, 10, Ds).
Ds = [1,2,3,4] ? ;
no

也可以反过来做!
| ?- n_base_digits(I,10,[1,2,3]).
I = 123 ? ;
no

请注意,上述方法比@mat在他的答案中提出的number_digits/3更快。(参见此处链接)

1
你可以避免使用递归,而是使用内置谓词进行类型转换:
my_digits(Number, List) :-
    atomic_list_concat(List, Atom),
    atom_number(Atom, Number).

第一行将列表转换为原子,第二行将该原子转换为数字,如果该数字与传入的数字相同,则返回true。
我不知道是否有更直接的方法将列表转换为数字(我认为没有...),如果有的话,可以在一行中完成。

0

我不同意 @ssBarBee。毕竟,如果你提供了你的列表并且他们的指控是正确的,你应该得到4321;但是相反,你得到了这个:

?- my_digits(Num, [1,2,3,4]).
ERROR: is/2: Arguments are not sufficiently instantiated

我们可以尝试使用 "clpfd" 这个模块来实现:
my_digits( 0, [] ).
my_digits(N,[A|As]) :- N1 #= N/10, A #= N mod 10, my_digits(N1, As).

我们得到了这个:
?- my_digits(Num, [1,2,3,4]), label([Num]).
Num = -6789 ;
Num = 4321.

我觉得这一切都很奇怪,但使用clpfd进行跟踪并不愉快。

如果你只想解析一个数字列表,我会倾向于将其变成尾递归,像这样:

my_digits(Num, List) :- my_digits(0, List, Num).

my_digits(Num, [], Num).
my_digits(N, [A|As], Num) :- N1 is N * 10 + A, my_digits(N1, As, Num).

这给了我们:
?- my_digits(Num, [1,2,3,4]).
Num = 1234 ;
false.

但它不会生成:

?- my_digits(1234, X).
ERROR: is/2: Arguments are not sufficiently instantiated

如果我不使用clpfd来解决这个问题,我会倾向于检查我的参数并编写单独的谓词。虽然有些丑陋,但这就是我会做的事情。
my_digits(Num, List) :- 
    nonvar(List), 
    my_digits_p(0, List, Num).
my_digits(Num, List) :- 
    var(List), 
    my_digits_g(Num, ListRev), 
    reverse(ListRev, List).

my_digits_p(Num, [], Num).
my_digits_p(N, [A|As], Num) :- N1 is N * 10 + A, my_digits(N1, As, Num).

my_digits_g(0, []) :- !.
my_digits_g(N, [A|As]) :- A is N mod 10, N1 is floor(N / 10), my_digits_g(N1, As).

这可以解析、检查或生成非变量的数字:

?- my_digits(1234, X).
X = [1, 2, 3, 4].

?- my_digits(X, [1,2,3,4]).
X = 1234 ;
false.

?- my_digits(1234, [1,2,3,4]).
true;
false.

如果您尝试使用两个参数作为变量进行生成,结果可能会非常不可靠:

?- my_digits(X, Y).
X = 0,
Y = [].

因此,我们可以尝试通过向my_digits添加另一个特殊情况来生成:

my_digits(Num, List) :- 
    var(Num), var(List), 
    my_digits_g_from(0, Num, ListRev), 
    reverse(ListRev, List).
my_digits(Num, List) :- 
    nonvar(List), 
    my_digits_p(0, List, Num).
my_digits(Num, List) :- 
    var(List), 
    my_digits_g(Num, ListRev),
    reverse(ListRev, List).

my_digits_g_from(N, N, List)   :- my_digits_g(N, List).
my_digits_g_from(N, Num, List) :- succ(N, N1), my_digits_g_from(N1, Num, List).

这是很多代码,也是一个不使用clp(fd)时必须进行的技巧演示。不幸的是,在Prolog中进行算术运算时,必须解决is不能统一的问题,但clp(fd)的复杂性证明了为什么会出现这种情况。

我希望有人能提供更优雅的解决方案!


我同意Daniel的观点。我匆忙发表了评论,导致不准确的言论。对于你在上面的代码所投入的工作和时间,我给予+1的赞许。 - ssbarbee
1
这种情况发生在我们所有人身上。谢谢! - Daniel Lyons
例如在查询 ?- my_digits(N, [_]). 上会导致实例化错误。 - mat
是的,它并不完美。正如我所提到的,这更多是一个展示在clp(fd)之外处理算术问题的难度的示例。如果您发现了解决方案,请告诉我。 - Daniel Lyons
1
简单:看看我发布的clp(fd)版本。如果即使对自己来说感觉很糟糕,为什么要用低级算术呢?你传达了你的最终版本是一个完成的替代品的印象,我只想明确指出,尽管我很欣赏你的努力,但它仍然不是完美的。 - mat

0
针对课堂作业,教授可能希望你提供以下类似的内容。通常来说,在分析问题陈述时,你应该首先确定特殊情况(在这种情况下是零和负值),然后再考虑一般情况。
: -- int_2_digits/2 ------------------------------------------------------------
: 
: The public api.
:
: we've got 2 special cases here:
: 
: * zero, and
: * negative numbers
:
: and, of course, the general case: a positive value.
:
: ------------------------------------------------------------------------------
int_2_digits( 0 , [0] ) .       : zero is a special case
int_2 digits( X , ['-'|Ds] ) :- : negative numbers are a special case
  X < 0 ,                       :   which we handle (YMMV) by prepending the
  X1 is - X ,                   :   sign and than processing the absolute value
  int_2_digits(X1,Ds) .         :
int_2_digits( X , Ds       ) :- : the general case is a positive value
  X > 0 ,                       : just invoke the worker predicate.
  int_2_digits(X,[],Ds) .       :

: -- int_2_digits/3 ------------------------------------------------------------
: 
: The guts of the operation.
: 
: We're using an accumulator here because we compute the result right-to-left,
: from least significant digit to most significant digit. Using the accumulator
: builds the list in the correst sequence, so we don't have to reverse it at
: the end.
: ------------------------------------------------------------------------------
int_2_digits( 0 , Ds , Ds ) .      : if we hit zero, we're done. Unify the accumulator with the result
int_2_digits( X , Ts  , Ds ) :-    : otherwise...
  D is mod(X,10) ,                 : - get the current digit (X modulo 10)
  T is div(X,10) ,                 : - get the next value via integer division
  int_2_digits( X1 , [T|Ts] , Ds ) : - recurse down
  .                                : Easy!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接