Search code examples
smalltalkpharocaesar-cipher

Creating a Caesar Cipher Method


So I need to get the Caesar Cipher code in smalltalk and create a method and use it so I can do the following test on it

|aString|
aString:=Caesar new encrypt: 'CAESAR'.
Transcript show: aString.

I already have the class made. But I need to make the method of it.

I found this but how can I make a method out of this so I can all the above code in playground.

| i c strCipherText strText iShiftValue iShift |

strText := 'the quick brown fox jumps over the lazy dog'.
iShiftValue := 3.

strCipherText := ''.
iShift := iShiftValue \\ 26.

i := 1.
[ i <= (strText size) ]
whileTrue: [
  c := (strText at: i) asUppercase.

  ( ( c >= $A) & ( c <= $Z ) )
  ifTrue: [

    ((c asciiValue) + iShift > $Z asciiValue)
    ifTrue: [
      strCipherText := strCipherText, (((c asciiValue) + iShift - 26)
                      asCharacter asString).
    ]
    ifFalse: [
      strCipherText := strCipherText, (((c asciiValue) + iShift)
                      asCharacter asString).
    ].

  ]
  ifFalse: [
    strCipherText := strCipherText, ' '.
  ].

  i := i + 1.
].

Transcript show: strCipherText.
Transcript cr.

So to make thing clear, I need to make a method using the Caesar Cipher code and use the "aString" code at the beginning and test it with that. I have this code above but this has already text in it and can't be put into the method.

Any help will be appreciated.


Solution

  • As Max said in his comment the code above can be put in a method. The only missing part is a first line with the selector and the formal argument:

    caesarCipherOf: strText
      <insert the code here>
    

    Another good suggestion by Max is to call the argument aString rather than strText because that's more aligned with how Smalltalkers name things.

    But now let's take a look at the source code itself:

    1. The comparison c >= $A & (c <= $Z) means c isLetter.
    2. The conditional calculation of the next character means that we want to shift-rotate c by moving it 3 characters to the right, wrapping it around if it gets beyond $Z. This can be easily expressed as:

      (c codePoint - 64 + 3 \\ 26 + 64) asCharacter
      

      where 64 = $A codePoint - 1, is the offset between $A and any given uppercase character c. Note also that I've replaced asciiValue with codePoint.

    With these two observations the method can be re-written as

        caesarCipherOf: aString
          ^aString collect: [:c |
            c isLetter
              ifTrue: [(c asUppercase codePoint - 64 + 3 \\ 26 + 64) asCharacter]
              ifFalse: [$ ]]
    

    This is not only shorter, it is more efficient because it avoids creating two new instances of String at every character. Specifically, any expression of the form

    string := string , <character> asString
    

    creates two Strings: one as the result of sending #asString, another as the result of sending the concatenation message #,. Instead, #collect: creates only one instance, the one that the method returns.