Assuming this is a simple substitution cipher we're talking about (and just for fun), I'll take a stab at it. One should note that this is completely untested.
I'm going to set this up in a generic way, so you can say something like:
substitution_cipher( CipherExpr , CipherResult , Expr , Result , Key ).
We'll make the rule that the enciphered stuff is represented by atoms, so you can say something like this:
substitution_cipher( ping + pong + fun , ignip , Expr , Sum , Key ) .
And get the results you'd expect.
So...
First, you need the set (discrete, unique) of characters found in the cipher text:
character_set( Expr , Charset ) :-
setof( C , A^Cs^( atoms_in_expression( Expr , A ) , atom_chars(A,Cs) , member(C,Cs) ) , Charset ) .
atom_in_expression( Expr , Value ) :- atom(Expr) .
atom_in_expression( Expr , Value ) :-
Expr =.. [ _ , Left , Right ] ,
(
values( Left , Value )
;
values( Right, Value
) .
The above walks the parse tree of an expression like a * b + c * d
, finding each of the leaf nodes (atoms), deconstructing them into the characters that comprise them. setof/3
ensures that the resulting list is sorted and unique.
Once you have that, you need a way of generating all the possible keys (key == mapping between a character and a digit). We want to be able to say something like
generate_key( [a,b,c] , Key )
and get back
Key = [a:1,b:2,c:3]
etc.
So:
generate_key( Charset , Key ) :-
generate_key( Charset , [] , Key ) .
generate_key( [] , Key , Key ) . % when we've exhausted the character set, we're done.
generate_key( [C|Cs] , Acc , Key ) :- % otherwise...for each character
digit(D) , % find a digit
\+ member( _:D , Acc ) , % that hasn't yet been used
generate_key( Cs , [C:D|Acc] , Key ) % map it to the character and recurse down.
.
digit(D) :- between(0,9,X) , atom_number(D,X).
Then you need a way to decode a cipher expression like
ping + pong + fun
and [try to] turn it back into proper numbers. This isn't much different than walking the parse tree and enumerating the leaf node atoms, but here we need to get them back into numeric form.
If the expression is an atom, we
- decompose it into its constituent characters,
- using our key, map each character to its corresponding digit,
then we turn that list of digits back into a number
decode( Key , CipherExpr , PlainExpr ) :-
atom(CipherExpression) ,
atom_chars(CipherExpression,Cs) ,
findall( D , ( member(C,Cs), member(C:D,Key) -> true ; D=C ) , Ds ) ,
number_chars( PlainExpr , Ds )
.
The general case is easy. An infix expression like ping + pong
is really the prolog term +(ping,pong)
. We:
- Decompose a infix term like
ping + pong
into an operator (+
) and its left and right sub-expressions.
- Then we recursively decode the left and right sub-expressions
Finally, we reassemble the [decoded] expression.
decode( Key , CipherExpr , PlainExpr ) :-
CipherExpr =.. [Op,L,R] ,
decode(L,L1) ,
decode(R,R1) ,
PlainExpr =.. [Op,L1,R1]
.
Then you can put it all together:
substitition_cipher( CipherExpr , CipherResult , PlainExpr , PlainResult , Key ) :-
character_set( CipherExpr = CipherResult , Charset ) ,
generate_key( Charset, Key ) ,
decode( Key , CipherExpr , PlainExpr ) ,
decode( Key , CipherResult , PlainResult ) ,
PlainResult =:= PlainExpr
.