Prolog tutorial – Matrices

This article can be a guide for solving some problems with Prolog. If you know it's syntax, maybe after reading this you will get an idea, how to use this language effectively.

Basic matrix operations and functions

When solving the exercise below, I firstly wrote predicates like "encode", "decode" etc. After that, I wrote those basic operations, so my approach was "from the top" and I thik it's the right approach. I decided to move these basic matrix operations from the end to the beginning, to make the code below more clear.

The list of N characters

This code creates a list of $n$ characters (zeros). It takes one characters and attach a list of $n-1$ characters to it. A list of zero characters is empty.

% spaces(+Num, -Spaces)
spaces(N, [0|Tail]) :- N > 0, N2 is N-1, spaces(N2, Tail).
spaces(0, []).

Concatenation of two lists

It uses a simple recursion. It goes deeply into recursion, and when going back, it ataches the items from the end of $X$ to the head of $Y$.

% concat(+X, +Y, -X.Y)
concat([X| T], L, [X | Rest]) :- concat(T, L, Rest).
concat([], L, L).

List reverse

This code reverses the list using "accumulator model". Predicate halves has 3 parameters $A, B, C$. I want it to be true, when $C$ is the concatenation of $A^{-1}$ (reversed A) and $B$. So I wrote the rule, that when you take a character from the head of $A$ and put it at the head of $B$, it's still true. When interpreter tries to evaluate the $C$, it uses this rule, untill $A$ is empty, then $B$ equals $C$.

% rev(+M, -M2) - reverse of a list
rev(M, M2) :- halves(M, [], M2).
% halves(+A, +B, -C).
halves([X|T], Acc, M2) :- halves(T, [X|Acc], M2).
halves([], X, X).

Checking, if the matrix consists of constant values $X$

A matrix consists of $X$s, when the first row consists of $X$s and other rows too. The row consists of $X$s, when it's first item is $X$ and other items too.

% mcontent(+M, +X) - check if there are only Xs in matrix
mcontent([R|Rest], X) :- rcontent(R, X), rcontent(Rest, X).
mcontent([], _).
rcontent([X|T], X) :- rcontent(T, X).
rcontent([], _).

Matrix transpose

Transpose of a square matrix only (!!!). Most interesting is the predicate firstCol. When the first row is $[H|T]$, the first item in column is $H$, the first item in "rest matrix" is $T$, other items are computed recursively. If you look at it for a moment, you might get an idea, but don't look at it for too long.

% trans(+M1, -M2) - transpose of square matrix
% 1. I get first column from Tail and make a first row (NT) from it
% 2. I transpose "smaller matrix" Rest into NRest
% 3. I take T and make it to be a first column of NTail
trans([[H|T] |Tail], [[H|NT] |NTail]) :- 
	firstCol(Tail, NT, Rest), trans(Rest, NRest), firstCol(NTail, T, NRest).
trans([], []).
% firstCol(+Matrix, -Column, -Rest)  or  (-Matrix, +Column, +Rest)
firstCol([[H|T] |Tail], [H|Col], [T|Rows]) :- firstCol(Tail, Col, Rows).
firstCol([], [], []).

Exercise #1: Text encoding / decoding

You have a text and you have to split it into matrices 4x4. Then you have a "mask matrix", you can see only 4 items of the matrix through this mask. Then you can rotate it and see other 4 items, rotate it again etc., after 4 rotations you have seen all the items.

Decoded textEncoded textExample of the mask matrix
Hello, how are you?
H e l l    o u ? _
o , _ h    _ _ _ _
o w _ a    _ _ _ _
r e _ y    _ _ _ _
1 1 1 _
_ _ 1 _
_ _ _ _
_ _ _ _

Encoding

Algorithm: We just take a letter after letter and insert them into empty matrices, until there are no letters left.

Input: A list of characters (their ASCII codes in prolog).

Output: A list of matrices 4x4.

Approach: We create a predicate "encode", which recursively adds matrices into list. Then predicate "matrix", which adds rows into matrix. Predicate "row" adds characters into row.

% encode(+ Text, -[Matrices])
encode(Text, [Mat|Tail]) :-	Text \= [], matrix(Text, Mat, 4, Rest), encode(Rest, Tail).
encode([], []).

% matrix(+ Text, -[Rows...], + NumOfRows, -RestOfText])
matrix(Text, [Row|Tail], N, RText) :-
	N \= 1, row(Text, Row, 4, Rest), N2 is N-1, matrix(Rest, Tail, N2, RText).
matrix(Text, [Row], 1, RText) :- row(Text, Row, 4, RText).

% row(+ Text, -[items...], + NumOfItems, -RestOfText])
row([X|Y], [X|Tail], N, RText) :- N > 0, N2 is N-1, row(Y, Tail, N2, RText).
row([], Items, N, []) :- spaces(N, Items).
row(Text, [], 0, Text).

Checking the mask matrix

Algorithm: When the 4x4 mask is correct? We can see, that "a middle mask" 2x2 should have only one slot. Other 3 slots should be around it. Every added slot of these 3 slots occupies 3 more places, which are symmetric to it.

When checking the mask, first we chech the middle mask - there should be exactly one slot. Then we check the border, we should find 3 slots there. For each slot, the places symmetric to it should be empty.

$ middleOK(M) \wedge borderOK(M) \rightarrow maskOK(M) $

UPDATE: I found much simpler solution. We just create 3 new copies of the mask. We rotate them 90, 180 and 270 degrees and add them one to another. We should get a matrix of ones.

Input: A mask matrix, 0 for empty, 1 for slot.

Output: Correct: true / false.

Approach: A mask is correct, when rotated copies of it, added one over another, give us a matrix of ones. Matrix is rotated, when it's rows are reversed and it's transposed.

% maskOK(+M)
maskOK(M) :- rot(M,M2), rot(M2, M3), rot(M3,M4), add(M, M2, M3, M4, Res), mcontent(Res, 1).

% rot(+M, -M2) - rotate clockwards
rot(M, M2) :- rev(M, M1), trans(M1, M2).

% add(+A, +B, +C, +D, -Out) - addition of 4 matrices
add([A1|R1], [A2|R2], [A3|R3], [A4|R4], [A|R] ) :- 
	addRow(A1, A2, A3, A4, A), add(R1, R2, R3, R4, R).
add([], [], [], [], []).
addRow([H1|T1], [H2|T2], [H3|T3], [H4|T4], [H|T]) :- 
	H is H1+H2+H3+H4, addRow(T1, T2, T3, T4, T).
addRow([], [], [], [], []).

Decoding

Algorithm: We should "apply" differently rotated mask on each encoded matrix, then put these pieces one on another and decode the final result.

Input: Encoded text (list of matrices), mask matrix.

Output: Decoded text (list of characters = ASCII codes)

We can decode input matrix immidiatelly, without using a mask. But let's make it more difficult and unneficient! :)

Approach: We make 4 differently rotated copies of the mask. We make 4 copies of encoded matrices. We apply masks on these lists of matrices. Then we put the lists of matrices one over another to get the former text. When decoding, we just take each matrix, each row of it, each char from the row, ant put it all into one big list.

% decode(+Code, +Mask, -Text).
decode(C1, M1, Text) :- c(C1, C2), c(C2, C3), c(C3, C4), 
	rot(M1, M2), rot(M2, M3), rot(M3, M4), 
	apply(C1, M1, O1), apply(C2, M2, O2), apply(C3, M3, O3), apply(C4, M4, O4), 
	addMany(O1, O2, O3, O4, O),  % now "C1" should be equal to "O"
	dec(O, Text).
	
% apply(+Matrices, +Mask, -Masked)
apply([C|Tail], M, [NC|NTail]) :- applyMask(C, M, NC),  apply(Tail, M, NTail).
apply([], _, []).

applyMask([R|Rest], [MR|MRest], [NR|NRest]) :- 
	applyRow(R, MR, NR), applyMask(Rest, MRest, NRest).
applyMask([], [], []).

applyRow([_|ITail], [0|MTail], [0|NTail]) :- applyRow(ITail, MTail, NTail).
applyRow([I|ITail], [1|MTail], [I|NTail]) :- applyRow(ITail, MTail, NTail).
applyRow([], [], []).

addMany([A1|R1], [A2|R2], [A3|R3], [A4|R4], [A|R] ) :- 
	add(A1, A2, A3, A4, A), addMany(R1, R2, R3, R4, R).
addMany([], [], [], [], []).

% dec(+M, -Text)
dec([M|Rest], Text) :- decMat(M, T), dec(Rest, RT), concat(T, RT, Text).
dec([], []).
decMat([R|Rest], Text) :- decRow(R, T), decMat(Rest, RT), concat(T, RT, Text).
decMat([], []).
decRow([X|T], [X|RT]) :- X>0, decRow(T, RT).
decRow([0|_], []).
decRow([], []).

c(X, Y) :- copy_term(X, Y).

Old comments (closed because of spam)