This is a read-only archive of lispforum.com. The forum was locked to new users and posts and is preserved here as static HTML from a database snapshot taken on 2019-09-07.

Newbie needs help please

21 posts · 5991 views

Hello all,

I am a big LISP-Newb and am struggling with a program that is supposed to encrypt a string with Caesar's cipher. What I have in mind is a function that takes in 2 arguments: a string and a number to specify the modulus. Something along the lines:

(defun c-encrypt (string number)
("lots of helper functions"))

My thoughts so far are that it would be best to set the alphabet as a parameter and write a function that looks up a chars position in the parameter and then shifts it the wanted Caesar's modulo to the right e.g. A ->modulo 7 -> H

May look something along the lines:

(defun shift (char x) (char *alphabet* (mod(+24 x"being the wanted modulo")27)))

Well, I ran headfirst into a lot of problems. First of all, I can not find a function to split a string into its single chars (convert it to numbers fine, but simply split it...). If I could somehow split the string into its single characters and make a list out of them my first problem would be solved. When trying to concatenate there are always these darn #\ left which make it impossible to use the parameter.

As you see I already fail at the first obstacle of the whole project... :oops: Not to speak about the rest...

Any help would be greatly appreciated since I really start to get frustrated about this project and LISP itself. For some reason I find it extremely difficult to "think LISP".

Thanks already for your answers!

Re: Newbie needs help please

RaydPanda wrote:I am a big LISP-Newb
First, the language family is called Lisp, and hasn't been called LISP since the, I believe, seventies, when non-capital letters were invented ;-)
RaydPanda wrote:First of all, I can not find a function to split a string into its single chars (convert it to numbers fine, but simply split it...).
That is meaningless. A string is a sequence of characters. It is already as split as it is going to be. All functions in sequences dictionary work on strings. For this application you do not want the list anyway, a vector and hash-table (in theory you could o it without the lookup by manipulating char-codes, but this is not general) is better.

Re: Newbie needs help please

RaydPanda wrote: Well, I ran headfirst into a lot of problems. First of all, I can not find a function to split a string into its single chars (convert it to numbers fine, but simply split it...). If I could somehow split the string into its single characters and make a list out of them my first problem would be solved. When trying to concatenate there are always these darn #\ left which make it impossible to use the parameter.
Quick and dirty:
> (loop for c across "foo" collect c)
(#\f #\o #\o)
> (coerce * 'string)
"foo"
-a

Re: Newbie needs help please

Thanks for your posts!

@Ramarren

Well, I guess my university course is not the best since I wasn't told about sequences and my teachers use "LISP" as if it is the correct form... thanks for the enlightenment! I'll try my luck with these, maybe I get some results. :)

@sinnatag

My problem with the first of your suggestions is that I need a list without the #\, otherwise I can not work with my alphabet parameter. Any suggestions on that?

Re: Newbie needs help please

RaydPanda wrote:@Ramarren

Well, I guess my university course is not the best since I wasn't told about sequences and my teachers use "LISP" as if it is the correct form... thanks for the enlightenment! I'll try my luck with these, maybe I get some results. :)
Unfortunately many teachers learnt Lisp when it was still a LISP and refused to learn anything more than necessary for their assignments to actually work in modern Lisp. Sometimes.
RaydPanda wrote:@sinnatag

My problem with the first of your suggestions is that I need a list without the #\, otherwise I can not work with my alphabet parameter. Any suggestions on that?
#\ is just syntax for characters. A string is a sequence of characters. Your alphabet should be a sequence of characters. It will work. If it doesn't work, then you are doing it wrong.

For the Caesar's cipher you need to look up the source characters position in the alphabet, add to it the shift modulo the length of the alphabet and then get the character from the alphabet. This can be done character by character.

Re: Newbie needs help please

RaydPanda wrote: My problem with the first of your suggestions is that I need a list without the #\, otherwise I can not work with my alphabet parameter. Any suggestions on that?
You can convert characters to symbols with something like this:
(intern (make-string 1 :initial-element (char-upcase #\a)))
For a real program this would just be making things difficult for yourself, but hopefully it will let you return to your assignment (which probably expects a solution based on lists and symbols or somesuch).

-a

Re: Newbie needs help please

sinnatagg wrote:
RaydPanda wrote: My problem with the first of your suggestions is that I need a list without the #\, otherwise I can not work with my alphabet parameter. Any suggestions on that?
You can convert characters to symbols with something like this:
(intern (make-string 1 :initial-element (char-upcase #\a)))
This way is simpler:
(intern (string (char-upcase #\a)))

Re: Newbie needs help please

gugamilare wrote: This way is simpler:
Yeah, I was kind of wondering why there wasn't a function like this in CL, aside from coerce() which doesn't work for this case.

-a

Re: Newbie needs help please

First off, thanks to all of you!

By now I got far enough to encrypt a single character. I try now to get it working on a string but to no avail so far.
The code I have until now looks like this:
; Alphabet as parameter plus one whitespace at index 0

(defparameter *alphabet* " abcdefghijklmnopqrstuvwxyz,.0123456789")

; Finds the index of char in  *alphabet*:

(defun alphabet-index (char)
  (position char *alphabet*))
 
; Finds the char shifted n places to the right in the alphabet:

(defun rotate (char n)
  (elt *alphabet* (mod (+ (alphabet-index char) n)(length *alphabet*))))
I am not sure how to use the map function with this. I tried (map 'string #rotate) but that doesn't seem to be the correct method. How do I get my rotate function to work on a whole string?

Re: Newbie needs help please

RaydPanda wrote:I am not sure how to use the map function with this. I tried (map 'string #rotate) but that doesn't seem to be the correct method. How do I get my rotate function to work on a whole string?
The signature of the map function is:
map result-type function &rest sequences+ => result
which, for this case, means you first need a function of one argument, the character, since you only have one sequence to be mapped, the encrypted string. This can be made using lambda, like this (the "#'" here is really optional, but I like it as a marker that a function is created here):
(defun make-rotator (n)
  #'(lambda (char)
      (rotate char n)))
This defines a function returning a closure, because the shift argument is closed over. You could also just use lambda directly in the map form, but I think this is clearer.

Then you can simply use that closure as an argument to map:
CL-USER> (map 'string (make-rotator 5) "ala ma kota")
"fqferfeptyf"

Re: Newbie needs help please

It works! At least on single words. But non the less, thank you! I really didn't think I'd ever come this far, you helped me a lot and gave me the right hints.

My program looks now like this:
; 1) Program that takes in a word(given as a string) plus a number and encrypts the word using Caesar's Cipher in the modulo given as the number:

; Alphabet as parameter plus one whitespace at index 0

(defparameter *alphabet* " abcdefghijklmnopqrstuvwxyz,.0123456789")

; Finds the index of char in  *alphabet*:

(defun alphabet-index (char)
  (position char *alphabet*))
 
; Finds the char shifted n places to the right in the alphabet:

(defun rotate (char n)
  (elt *alphabet* (mod (+ (alphabet-index char) n)(length *alphabet*))))

; makes the rotate function accesable for map

(defun e-helper (n)
  #'(lambda (char)
      (rotate char n)))


; Returns the encrypted string
 
(defun encrypt (str n)
  (map 'string (e-helper n) str)) 

; 2) Program that decrypts a word given as a string (first argument) using Caesar's cipher in the modulo given (second argument)

; Rotates the encrypted chars back to their original meaning

(defun rotate-back (char n)
  (elt *alphabet* (mod (- (alphabet-index char) n)(length *alphabet*))))

; Makes the rotate-back function accesable for map

(defun d-helper (n)
  #'(lambda (char)
      (rotate-back char n)))

; Returns the decrypted string

(defun decrypt (str n)
  (map 'string (d-helper n) str))
For some reason Lisp gives me an error message when I try a sentence instead of a single word, is there something wrong with my parameter? The given error is:
+: NIL is not a number
[Condition of type SIMPLE-TYPE-ERROR]

Some idea what went wrong?

Re: Newbie needs help please

RaydPanda wrote: For some reason Lisp gives me an error message when I try a sentence instead of a single word, is there something wrong with my parameter? The given error is:
+: NIL is not a number
[Condition of type SIMPLE-TYPE-ERROR]

Some idea what went wrong?
Your alphabet-index() function probably returns nil, while the +() function expects numbers as its arguments. I'ld guess your sentence contains upper case characters which can't be looked up in the *alphabet* string, and position then returns nil.

-a

Re: Newbie needs help please

What sinnatagg said. To make it more explicit you could change
RaydPanda wrote:
(defun alphabet-index (char)
  (position char *alphabet*))
to
(defun alphabet-index (char)
  (or (position char *alphabet*)
      (error "There is no '~a' in alphabet." char)))
which will trigger a more descriptive error.

Re: Newbie needs help please

Ah, I didn't realize that the uppercase letter was the problem, I thought the white space caused the error. Now I got the problem fixed by simply changing my *alphabet* to:
(defparameter *alphabet* " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ,.0123456789")
Which gives me the following results:
CL-USER> (encrypt "This is a sentence" 3)
"vIJTbJTbBbTFOUFODF"
CL-USER> (decrypt "vIJTbJTbBbTFOUFODF" 3)
"This is a sentence"
Problem solved and first part of the project finished, I'm so happy as you wouldn't believe.
Thanks again for all the help!

Now I have to enhance the program so that it can also use Vigenère cipher. Let's see how that works:

I know that I can express Vigenere algrebraically as Ciphertextletter index = Plaintextletter index + Keywordletter index (mod length of the used alphabet)

SO what I need to do is write some functions that match the length of the plaintext and keyword (by repeating the keyword), then look up the index of the equaling characters in the two string, add them together and feed them to an altered rotate function.

So much for the theory... the programing itself is a bit harder then the simple logic behind it, unfortunately. But I'm sure that I can get it right with your help :D

Re: Newbie needs help please

RaydPanda wrote:So much for the theory... the programing itself is a bit harder then the simple logic behind it, unfortunately. But I'm sure that I can get it right with your help :D
Unfortunately this cannot be done using just mapping (at least without producing the index range as a list, which is silly), so you might want to read the LOOP chapter in PCL if you have not already. You can't collect from LOOP directly into the string, but that is what COERCE is for.

Personally I don't really like LOOP, preferring ITERATE, and this particular problem has pretty nice SERIES solution actually, but I suppose one would like to avoid dependencies for homework. Sometimes I wish SERIES was included in the standard, instead of ending as just an appendix to CommonList The Language 2ed.

Re: Newbie needs help please

Hm, I don't seem to get it right... what I managed until now is a function that actually encrypts a character with a key character. I need to find a possibility to use that function on two strings instead of two characters. The functions so far look like this:
; 1) Program that takes in a word(given as a string) plus a number and encrypts the word using Caesar's Cipher in the modulo given as the number:

; Alphabet as parameter plus one whitespace at index 0

(defparameter *alphabet* " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ,0.1?2!3;4:56789")

; Finds the index of char in  *alphabet*:

(defun alphabet-index (char)
  (position char *alphabet*))
 
; Finds the char shifted n places to the right in the alphabet:

(defun rotate (char n)
  (elt *alphabet* (mod (+ (alphabet-index char) n)(length *alphabet*))))

; makes the rotate function accesable for map

(defun e-helper (n)
  #'(lambda (char)
      (rotate char n)))


; Returns the encrypted string
 
(defun encrypt (str &optional (n 7))
  (map 'string (e-helper n) str)) 

; 2) Program that decrypts a word given as a string (first argument) using Caesar's cipher in the modulo given (second argument)

; Rotates the encrypted chars back to their original meaning

(defun rotate-back (char n)
  (elt *alphabet* (mod (- (alphabet-index char) n)(length *alphabet*))))

; Makes the rotate-back function accesable for map

(defun d-helper (n)
  #'(lambda (char)
      (rotate-back char n)))

; Returns the decrypted string

(defun decrypt (str &optional (n 7))
  (map 'string (d-helper n) str))

; 3) Program that takes a string and a keyword and encrypts the string using Vigenere cipher

; C(iphertext)index= P(laintext)index+K(ey)index (mod alphabetlength)

; Function that looks up index of equaling characters in plaintextstring and repeated keywordstring and adds them together

(defun add-indexes (P K)
  (+ (alphabet-index P) (alphabet-index K))))
  
; Function that rotates the characters of the plaintext modulo the calculated shift from add-indexes

(defun v-rotate (P K)
  (rotate P (add-indexes P K))))
I looked through the loop descriptions but didn't find something that struck me as the right one. I started testing out loop for i from 0 (length plaintext) and loop across 'string... but didn't get anywhere near what I need.
May I have some more hints?

P.S. I may not use iterate since it is not in the standard, sorry.

Re: Newbie needs help please

Some of your parentheses seem unbalanced.
RaydPanda wrote:I looked through the loop descriptions but didn't find something that struck me as the right one. I started testing out loop for i from 0 (length plaintext) and loop across 'string... but didn't get anywhere near what I need.
May I have some more hints?
I don't know how to give a hint, because, at least in retrospect this is really obvious, and I don't like LOOP anyway so I wouldn't want to inflict careful reading of the specification on someone, so:
(defun vigenere (string key)
  (coerce (loop with length-string = (length string)
                with length-key = (length key)
                for i from 0 below length-string
                collect (v-rotate (char string i) (char key (mod i length-key))))
          'string))
For comparision, ITERATE solution:
(defun vigenere-iterate (string key)
  (coerce (iter (with length-key = (length key))
                (for c in-string string with-index i)
                (collect (v-rotate c (char key (mod i length-key)))))
          'string))
SERIES solution, unfortunately cluttered by an utility function:
(defun scan-string-loop (string)
  (declare (optimizable-series-function 2))
  (let ((length (length string)))
    (scan-fn '(values character (integer 0 #.array-dimension-limit))
             #'(lambda ()
                 (values (char string 0) 0))
             #'(lambda (char index)
                 (declare (ignore char))
                 (let ((new-index (if (= (1+ index) length)
                                      0
                                      (1+ index))))
                   (values (char string new-index) new-index))))))

(defun vigenere-series (string key)
  (series:collect 'string
    (mapping ((c (scan 'string string))
              (k (scan-string-loop key)))
             (v-rotate c k))))

Re: Newbie needs help please

I often skip the loop languages and just use the straightforward macros. Here's a reduced consing solution (not tested).
(defun vigenere (string key)
  (let* ((length-string (length string))
         (length-key (length key))
         (result (make-string length-string)))
    (dotimes (i length-string)
      (setf (char result i)
            (v-rotate (char string i) (char key (mod i length-key)))))
    result))

Re: Newbie needs help please

:shock:

You guys are unbelievable! Thanks a lot! And yes, it really seems not this hard in retrospect, but that is nearly always the case. ;)

Now I only have to find out how to make the matching decryption and I'm done for good. Unfortunately something along the following lines doesn't work since it simply produces a string of the repeated keyword:
; P(laintext)index = C(ipehertext)index -  K(ey)index (mod alphabetlength) => Decryption


; Function that looks up the index of a ciphertext character and it's equaling key character and subtracts them from each other

(defun substract-indexes (C K)
  (- (alphabet-index C) (alphabet-index K))))

; Function that rotates the Ciphertexts char back to its Plaintext position

(defun v-rotate-back (C K)
  (rotate-back C (substract-indexes C K)))

; returns decrypted string

(defun v-decrypt( (string key)
  (coerce (loop with length-string = (length string)
                with length-key = (length key)
                for i from 0 below length-string
                collect (v-rotate-back (char string i) (char key (mod i length-key))))
          'string))
So back into the loop fun, or maybe the problem lies within the rotation. I'm not sure if the loop can take in negative numbers. I'll see if I can figure it out. Maybe I'll get it right.

Thanks again for your help! All of you are making this project interesting for me again, and I really learn a lot by looking at your code and seeing the different possibilities.

Re: Newbie needs help please

I haven't noticed it before, but you are rotating wrong, since you computer the sum/difference of indexes, and then add them to character index again in rotate/rotate-back. Either drop the add/subtract-indexes and just do:
(defun v-rotate (P K)
  (rotate P (alphabet-index K)))

(defun v-rotate-back (C K)
  (rotate-back C (alphabet-index K)))
Or don't rotate, just return character at computed index.

Re: Newbie needs help please

I came to the same conclusion just before I saw your post... and what wonder: Now the decryption works as well. :D
I was thinking too strictly in terms of the already existing functions so I didn't notice the algebraic error, silly me. Well, the nice thing is that I now have a fully functional program. This is the complete project:
; Encryption-Project by Cara Petrovitsch

; 1) Program that takes in a word(given as a string) plus a number and encrypts the word using Caesar's Cipher in the modulo given as the number:

; Extended alphabet as parameter plus one white space at index 0:

(defparameter *alphabet* " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ,0.1?2!3;4:5@6-7_8'9")

; Finds the index of char in  *alphabet*:

(defun alphabet-index (char)
  (position char *alphabet*))
 
; Finds the char shifted n places to the right in the alphabet:

(defun rotate (char n)
  (elt *alphabet* (mod (+ (alphabet-index char) n)(length *alphabet*))))

; Makes the rotate function easier to use with map:

(defun e-helper (n)
  #'(lambda (char)
      (rotate char n)))

; Returns the encrypted string (when no modulo is specified explicitly, modulo 7 is used):
 
(defun c-encrypt (str &optional (n 7))
  (map 'string (e-helper n) str))
 

; 2) Program that decrypts an already encrypted  word given as a string (first argument) using Caesar's cipher in the modulo given (second argument)

; Rotates the encrypted chars back to their original meaning:

(defun rotate-back (char n)
  (elt *alphabet* (mod (- (alphabet-index char) n)(length *alphabet*))))

; Makes the rotate-back function easier to use with map:

(defun d-helper (n)
  #'(lambda (char)
      (rotate-back char n)))

; Returns the decrypted string (when no modulo is specified explicitly, modulo 7 is used): 

(defun c-decrypt (str &optional (n 7))
  (map 'string (d-helper n) str))


; 3) Program that takes a plaintext and a keyword (given in stringformat) and encrypts the plaintext using Vigenere cipher

  
; Function that rotates the characters of the plaintext modulo the key's index:

(defun v-rotate (P K)
  (rotate P (alphabet-index K))))

; Returns the encrypted string:

(defun v-encrypt (string key)
  (coerce (loop for i from 0 to (1- (length string))
                collect (v-rotate (char string i) (char key (mod i (length key)))))
          'string))

; 4) Program that takes a ciphertext and a keyword (given in stringformat) and decrypts the ciphertext using Vigenere cipher
   

; Function that rotates the Ciphertexts char back to its Plaintext position:

(defun v-rotate-back (C K)
  (rotate-back C (alphabet-index K)))

; returns the decrypted string:

(defun v-decrypt (string key)
  (coerce (loop for i from 0 to (1- (length string))
                collect (v-rotate-back (char string i) (char key (mod i (length key)))))
          'string))
I am so happy with it, my first working program! :P

Thank you once again! (I start sounding like a scratched plate... don't I?)