Пролог, преобразующий целое число в список цифр

Я хочу написать предикат, что целое число и список цифр, и добиться успеха, если цифры содержат цифры целого числа в правильном порядке, то есть:

?-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).

person Duc Hai    schedule 16.04.2013    source источник
comment
Вы уже пробовали что-нибудь? Нехорошо просить помощи с домашним заданием, ничего не делая.   -  person Daniel Lyons    schedule 17.04.2013
comment
Попробуйте перевернуть список перед вызовом my_digits/2, потому что тогда будет применяться ваша логика...   -  person ssBarBee    schedule 17.04.2013


Ответы (6)


Как уже предлагалось, рассмотрите возможность использования ограничений конечной области:

:- 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.
person mat    schedule 17.04.2013
comment
Digit in 1..9 это опечатка? Вместо этого я ожидал Digit in 0..9. - person Wouter Beek; 24.11.2015
comment
Да, это была опечатка. Спасибо! - person mat; 24.11.2015

Я думаю, что это проще:

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).
person user8354592    schedule 23.07.2017
comment
Хотя я поклонник простоты этого подхода, следует отметить, что, к сожалению, он не работает в обоих направлениях (предоставление ему списка не создаст число), к чему мы должны стремиться в Прологе, - person Gabriel Jablonski; 25.12.2020

Вот еще один вариант, основанный на clpfd... На основе (#=)/3 и if_//3 мы определяем:

n_base_digits(N, R, Ds) :-
   N #> 0,                                  % positive integers only
   R #> 1,                                  % smallest base = 2
   Ds = [D|_],                              % leading digit may not be 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

Обратите внимание, что вышеприведенное быстрее, чем number_digits/3, как было предложено @mat в его ответе.

person repeat    schedule 14.06.2016

Вы также можете избежать рекурсии и использовать встроенные предикаты для преобразования типов:

my_digits(Number, List) :-
    atomic_list_concat(List, Atom),
    atom_number(Atom, Number).

Первая строка преобразует список в атом, а вторая строка преобразует этот атом в число, что даст true, если это число совпадает с переданным.

Я не знаю, есть ли еще более прямой способ преобразовать список в число (не думаю..), и в этом случае это можно было бы сделать в одной строке.

person magus    schedule 17.04.2013

Я не согласен с @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). К сожалению, при выполнении арифметических операций на Прологе приходится обходить тот факт, что is не унифицирует, но сложность clp(fd) является хорошим доказательством того, почему это так.

Я надеюсь, что у кого-то есть более элегантное решение!

person Daniel Lyons    schedule 17.04.2013
comment
Согласен Даниил. Я поторопился с комментарием, который привел к неточному. +1 за работу и время, которые вы вложили в код выше. - person ssBarBee; 17.04.2013
comment
Это случается со всеми нами. Спасибо! - person Daniel Lyons; 17.04.2013
comment
Это приводит к ошибке создания экземпляра, например, в запросе ?- my_digits(N, [_]). - person mat; 17.04.2013
comment
Да, это не идеально. Как я уже упоминал, это скорее демонстрация того, как сложно иметь дело с арифметикой за пределами clp(fd). Если вы видите простой способ исправить это, я хотел бы это знать. - person Daniel Lyons; 17.04.2013
comment
Просто: посмотрите версию clp(fd), которую я опубликовал. Зачем делать это с помощью низкоуровневой арифметики, если это кажется отвратительным даже вам? Вы создали впечатление, что ваша окончательная версия является законченной альтернативой, я только хотел прямо указать, что, несмотря на ваши усилия (которыми я восхищаюсь), это все еще таковым не является. - person mat; 18.04.2013
comment
Спасибо, и я согласен. :) +1 - person Daniel Lyons; 18.04.2013

Для классного задания? Вероятно, профессор ищет что-то вроде следующего. Как правило, ваш анализ постановки задачи должен сначала выявить частные случаи (в данном случае нулевые и отрицательные значения), а затем общий случай.

: -- 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!
person Nicholas Carey    schedule 18.04.2013