2

I'm writing a cipher in Prolog but I'm not sure how to pass arguments so I can both cipher and decipher the input.

caesar(L1,L2,L3) :-
    maplist(cipher, L1, PC), string_to_list(L2,PC),
    maplist(decipher,L2,PC), string_to_list(L3,PC).

cipher(A,B) :-
    B is A+1.
decipher(A,B) :-
    B is A-1.
false
  • 10,264
  • 13
  • 101
  • 209
J252
  • 23
  • 3

2 Answers2

2

I think there are a few of issues with your code.

Your description says, ...so I can both cipher and decipher the input. That would imply that L3 is supposed to be the deciphering of L1. Your code deciphers L2 instead (which is the output of ciphering L1).

The second issue is your reuse of the PC variable. Once a variable is instantiated in Prolog within a predicate clause, it can't be reinstantiated without backtracking. So you need a new variable, or the second line in your clause will fail.

So a corrected version might look like this if you're just dealing with lists:

caesar(L1, L2, L3) :-
    maplist(cipher, L1, Encode), string_to_list(L2, Encode),
    maplist(decipher, L1, Decode), string_to_list(L3, Decode).

If you really did intend to have L3 be the deciphering of the result of L1 being ciphered, then replace L1 by L2 in the last line. Also, as @false notes, string_to_list/2 is deprecated in SWI Prolog in favor of string_codes/2.

Note that, in Prolog, the expression "abc" and [97, 98, 99] represent the same thing. Prolog treats them as equivalent and you can actually use the interchangeably. If you wanted to use atoms (single quotes), then you'd use atom_codes/2:

caesar2(L1, L2, L3) :-
    atom_codes(L1, List1),
    maplist(cipher, List1, Encoded), atom_codes(L2, Encoded),
    maplist(decipher, List1, Decoded), atom_codes(L3, Decoded).

Which would yield the same result, but you'd use single quotes for the atoms.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • 1
    @false, I see SWI Prolog says `string_to_list/2` is deprecated in favor of the built-in `string_codes/2`, but the latter isn't recognized in my SWI prompt for some reason. – lurker Dec 03 '14 at 17:37
  • @false, yep no worries. My SWI is 6.0.2 so it's probably not in my version. – lurker Dec 03 '14 at 18:14
  • Upgrading up to 6.6 is OK. But with 7 things fall apart. – false Dec 03 '14 at 18:19
1

You give the testcase ?- caesar("hello",L2). which can be handled in several ways. Since SWI 7 the default are strings. But this is not a common encoding. For other encodings see this.

The most straight forward and traditional method is.

:- set_prolog_flag(double_quotes,codes).

After putting this directive into your file, or issuing on the toplevel:

?- set_prolog_flag(double_quotes,codes).
   true. 
?- L = "hello".
   L = [104,101,108,108,111].

Now, the method to encode can be directly applied. Julius Caesar used the method a bit differently from what you have indicated. In particular did he use modulo, and sometimes different offsets. Also, ASCII was unknown to him, only capital letters, no digits (only like XVII). Permit me to include the new fangled letters G (C with cauda), J (I with cauda), U (fancy V), W (fancy VV). Otherwise we would need an explicit table.

Here is the general cypher relation using library(clpfd). 0'A is the character code of A, 0'Z that of Z.

:- use_module(library(clpfd)).

cypher(Code, A,B) :-
   [A,B] ins 0'A..0'Z, % only letters!
    B #= 0'A+((A-0'A)+Code)mod 26.

Note that this is a relation ; it can be used to encode:

?- cypher(1, 0'A, Y).
   Y = 0'B.

... and also to decode:

?- cypher(1, X, 0'B).
   X = 0'A.

So finally we can apply this relation to lists of codes:

?- maplist(cypher(1),"ABC",L), atom_codes(A, L).
   L = [66,67,68], A = 'BCD'.
?- maplist(cypher(1),"ABC","BCD").
   true.
?- maplist(cypher(1),X,"BCD"), atom_codes(A, X).
   X = [65,66,67], A = 'ABC'.

Pretty annoying to always be forced to convert the integers back into something readable. Isn't it? Well, there is a better approach:

:- set_prolog_flag(double_quotes, chars).

Another method is to use chars, they have the big advantage that answer are now much more readable, but with library(double_quotes) they are perfectly readable. Simple download it.

?- set_prolog_flag(double_quotes, chars).
   true.
?- L = "ABC".
   L = ['A','B','C'].
?- use_module(double_quotes).
   true.
?- L = "ABC", L = [C|Cs].
   L = "ABC", C = 'A', Cs = "BC".

However, our cypher-relation get's now more complex, as we have to convert characters to codes:

xcypher(Code, ACh, BCh) :-
   ( nonvar(ACh) -> char_code(ACh, A) ; char_code(BCh, B) ),
   cypher(Code, A, B),
   char_code(ACh, A),
   char_code(BCh, B). 

And this is how Caesar read about it (proof):

?- maplist(xcypher(1),Xs, "SPNBOFTFWOUEPNWT").
  Xs = "ROMANESEVNTDOMVS".
false
  • 10,264
  • 13
  • 101
  • 209