• Aucun résultat trouvé

Calcul bancaire

Il s’agit de calculer la suite de versements successifs qu’il faut effectuer pour rembourser un capital emprunté à une banque. On suppose qu’entre deux versements s’écoule toujours le même temps et que pendant cette durée le taux d’intérêt réclamé par la banque est de 10%. L’ensemble des faits définis par le programme sera l’ensemble des arbres de la forme

versements_capital(X,C)

et où X représente la liste des versements nécessaires pour rembourser le capital C avec un intérêt de 10% entre deux versements. Le programme lui- même se résume à deux règles:

versements_capital(<>, 0) ->; versements_capital(<V>.X, C) ->

versements_capital(X, C+(10/100)C-V);

La première règle exprime qu’il n’est pas nécessaire de faire de versements pour rembourser un capital nul. La deuxième règle exprime que la suite des

et d’une suite X de N versements permettant de rembourser le capital C

augmenté de 10% d’intérêts mais diminué du versement V effectué.

Ce programme peut-être utilisé de différentes façons. Une des plus specta- culaires est de demander pour quelle valeur de V la suite de versements

<V,2V,3V> permet de rembourser 1000F. Il suffit de poser la requête

> versements_capital(<V,2V,3V>,1000); {V = 207 + 413/641}

Crypto-arihmétique

Le problème consiste à affecter aux 8 lettres s,e,n,d,m,o,r,y des chiffres tous différents de façon à ce que l’addition send+more=money tombe juste. La première façon de procéder consiste à profiter du fait que l’addition, la soustraction, la multiplication par une constante et les relations =,!,<,!,",> sont parfaitement connues au niveau des nombres rationnels.

On fait aussi intervenir le prédicat prédéfini enum(X) qui énumère tous les entiers X satisfaisant aux contraintes accumulées. Le programme est d’ailleurs purement déterministe jusqu’au moment où l’on appelle ce prédicat. Voici le programme : solution(I,J,K) -> chiffres_tous_differents(<S,E,N,D,M,O,R,Y>) tous_entiers(<M,S,O,E,N,R,D,Y>), { S # 0,M # 0, I = 1000 S + 100 E + 10 N + D, J = 1000 M + 100 O + 10 R + E, K = 10000 M + 1000 O + 100 N + 10 E + Y, I + J = K };

tous_entiers(<>) ->; tous_entiers(<X>.S) -> enum(X) tous_entiers(S); chiffres_tous_differents(<>) -> ; chiffres_tous_differents(<X>.S) -> ; hors_de(X,S) chiffres_tous_differents(S), {0 <= X <= 9}; hors_de(X,<>) ->;

hors_de(X,<Y>.S) -> hors_de(X,S),{X # Y};

Si l’on pose la requête

> solution(I,J,K);

{I=9567, J=1085, K=10652}

Remplissage d’un rectangle par des carrés

Voici maintenant un problème, tiré de l'article d'Alain Colmerauer, "une introduction à Prolog III" qui met bien en valeur la partie numérique de Prolog III. Etant donné un entier N on s'intéresse à savoir s'il existe N carrés de dimensions distinctes qui peuvent être assemblés pour former un rectangle. Dans l'affirmative, on aimerait bien entendu connaître les dimensions de ces carrés et du rectangle formé. Voici par exemple deux solutions à ce problème pour N=9.

15 18 8 7 4 14 1 10 9 33 32 33 36 28 5 2 9 25 7 16 69 61

On désignera par a le rapport de la longueur du plus grand côté avec celle du plus petit côté du rectangle construit. On peut évidemment supposer que la longueur du plus petit côté est 1 et que la longueur du plus grand côté est

a. Il faut donc remplir un rectangle de dimensions 1 x a par N carrés tous distincts. Ce remplissage se fera en plaçant successivement chaque carré dans la position la plus basse possible et, à hauteur égale, dans la position la plus à gauche possible. En se référant au schéma qui suit, la base de l'algorithme de remplissage consistera alors

(1) à placer un carré dans le coin inférieur gauche du rectangle, (2) à remplir de carrés la zone A, si elle n'est pas vide,

Le remplissage des zones A et B se fera récursivement de la même façon : placer un carré dans le coin inférieur gauche et remplir deux sous-zones.

A B

1

a

Les zones et les sous-zones sont séparées par des lignes brisées en forme d'escalier allant du coin supérieur droit des carrés au coin supérieur droit du rectangle. Ces lignes brisées ne descendent jamais et s'il est possible d'en tracer plusieurs pour aller d'un point à un autre on considère toujours la plus basse. Voici par exemple toutes les lignes de séparations correspondant à la première solution du problème lorsque N = 9 :

D'une façon plus précise une zone ou une sous-zone a la forme du schéma de gauche ci-dessous, la totalité du rectangle étant lui-même assimilé à la zone particulière dessinée à droite.

P Q L' L Q L' L P

La zone est délimitée par une ligne brisée inférieure L allant d'un point P à un point Q et par une ligne brisée supérieur L' allant du même point P au même point Q. Le point P est placé n'importe où dans le rectangle à remplir alors que le point Q désigne le coin supérieur droit du rectangle. Ces lignes brisées sont représentées par des suite alternées de segments verticaux et horizontaux

v0, h1, v1, ... , hn, vn,

vi désigne la longueur d'un segments vertical et hi la longueur d'un segment horizontal. Les hi sont toujours strictement positifs. Les vi sont soit nuls, soit positifs pour désigner des segments qui montent, soit négatifs pour désigner des segments qui descendent. Les vi des lignes supérieures ne sont jamais négatifs et si une zone n'est pas vide seul le premier segment vertical v0 de sa ligne inférieure est négatif.

Si l'on applique ces conventions à la totalité du rectangle, (figure droite précédente) la ligne inférieure L peut être représentée par la suite 1,a,1 et la ligne supérieure L' par une suite de la forme 0,h1,0,...,hn,0, avec

Le cœur du programme est la procédure

remplir_zone(L,L',C,C')

qui remplit de carrés une zone délimitée inférieurement par L et calcule sa délimitation supérieure L'. Les carrés sont puisés dans le début de la liste C

et C' est la liste de carrés qui restent. Cette procédure fait appel à la procédure

placer_carre(b,L,L')

qui place un carré de dimension b#x#b dans le coin inférieur gauche de la zone à remplir. Ici L désigne la ligne inférieure de la zone, mais de laquelle on a enlevé le premier segment vertical, et L' désigne la ligne descendant du coin supérieur droit du carré pour rejoindre et se prolonger dans le reste de la ligne L. La figure ci-dessous montre les trois cas qui se présentent. Soit le carré déborde sur la première marche, qui en fait était une fausse marche de hauteur nulle, soit le carré est collé contre la première marche, soit le carré n'est pas assez grand pour toucher la première marche.

L'

L L

L'

L L'

creer_carres(C) remplir_zone(<-1, A, 1>, L, C, <>) , { A >= 1 } ; creer_carres(<>) -> ; creer_carres(<B>.C) -> creer_carres(C) rendre_distinct(B, C), { B > 0 } ; rendre_distinct(B, <>) -> ; rendre_distinct(B, <B'>.C) -> rendre_distinct(B, C) , { B # B' } ; remplir_zone(<V>.L, <V>.L, C, C) -> , { V >= 0 } ; remplir_zone(<V>.L, L''', <B>.C, C'') -> placer_carre(B, L, L') remplir_zone(L', L'', C, C') remplir_zone(<V+B, B>.L'', L''', C', C'') , { V < 0 } ; placer_carre(B, <H, 0, H'>.L, L') -> placer_carre(B, <H+H'>.L, L') , { B > H } ; placer_carre(B, <H,V>.L, <-B+V>.L) -> , { B = H } ; placer_carre(B, <H>.L, <-B, H-B>.L) -> , { B < H } ;

L'appel général se fait par la requête

> remplir_rectangle(a, C), {C::n};

n est le nombre de carrés de tailles distinctes qui doivent remplir un rectangle. Le programme calcule la dimension 1 x a du rectangle (a""""1) et la liste C des dimensions des n carrés. Ce calcul débute par l'exécution de la première règle, qui à la fois contraint a à être plus grand ou égal à 1, crée n carrés (de tailles inconnues) tous distincts et lance le remplissage de la zone constituée par la totalité du rectangle. La ligne L constituant la délimitation

supérieure de cette zone est inconnue au départ, mais, compte tenu que cette ligne doit joindre deux points qui se trouvent à la même hauteur et qu'elle ne peut descendre, ce sera forcément une ligne horizontale représentée par un escalier dont toutes les marches sont de hauteur nulles. Si on lance la requête :

> remplir_rectangle(a, C), {C::9};

on obtient 8 réponses. Les deux premières que voici

{a = 33/32,

C = <15/32,9/16,1/4,7/32,1/8,7/16,1/32,5/16,9/32>} {a = 69/61,

C =

<33/61,36/61,28/61,5/61,2/61,9/61,25/61,7/61,16/61>}

correspondent aux deux assemblages que nous avons dessinés. Les 6 autres réponses décrivent des assemblages symétriques de ceux-ci.

Les contraintes booléennes

1. Introduction

2. Quelques définitions et remarques