Perl Weekly Challenge 97: Ceasar Cipher

You are given string $S containing alphabets A..Z only and a number $N.

Write a script to encrypt the given string $S using Caesar Cipher with left shift of size $N.

Overview

Wikipedia has a description of the Ceasar Cipher.

First thing we need to define is how to read the input. We’ve decided to read the plain text from standard input, and we use a command line option (-s SHIFT) to indicate the left shift. The encrypted text will be written to standard output.

Shifting will be done by looking at each characters, and if it is a capital letter, we look at its code point (ASCII or Unicode — which for letters A to Z is identical), and subtract the left shift. If the result is less than 65, we add 26. 65 being the code point of A, and 26 the number of letters. We than map the resulting code point back to a character.

Solutions

Perl

use Getopt::Long;

GetOptions 's=i'  =>  \my $shift;
die "-s SHIFT option required" unless defined $shift;
$shift %= $NR_OF_LETTERS;

We first parse to the command line. Getopt::Long does the work of parsing @ARGV for us. If no option is given, we die. Else, we mode the value (now in $shift) with 26, because shifting by 26 is a no-op.

We can now do the actual shifting:

my $NR_OF_LETTERS = 26;
my $ORD_A         = ord ('A');

while (<>) {
    chomp;
    s {([A-Z])}
      {   my $ch = ord ($1) - $shift;
          $ch += $NR_OF_LETTERS if $ch < $ORD_A;
          chr $ch
      }eg;
    say;
}

We iterate over the input, and for each capital letter found, we take its code point (ord), and subtract the given shift value. We add 26 if we end up below the code point of the letter A. Then we translate the number back to a (one letter) string (chr).

Find the complete program on GitHub.

AWK

AWK does not have a method to map a character to its code point. We therefor start by building an array (ord), so we can look up the code point. In AWK, we can index arrays with strings:

BEGIN {
    NR_OF_LETTERS = 26
    ORD_A         = 65

    for (i = ORD_A; i < ORD_A + NR_OF_LETTERS; i ++) {
        t = sprintf ("%c", i)
        ord [t] = i
    }
}

We then need to parse the command line option:

BEGIN {
    for (i = 1; i < ARGC; i ++) {
        if (ARGV [i] == "-s") {
            shift = ARGV [i + 1]
        }
    }
    ARGC = 0
}

ARGC is the number of elements in ARGV, which, unlike most arrays in AWK, is indexed starting from 0. We’re scanning the array starting from index 1, as ARGV [0] contains the name of the executable (typically awk).

Note the assignment ARGC = 0. If we don’t do this, awk itself will treat the options as subsequent files to be executed.

Now the shifting of the characters:

{   
    out = ""
    for (i = 1; i <= length (letters); i ++) {
        char = substr ($0, i, 1)
        if (ord [char]) {
            n = ord [char] - shift
            if (n < ORD_A) {
                n = n + NR_OF_LETTERS
            }
            char = sprintf ("%c", n)
        }
        out = out char
    }
    print out
}

AWK does not have a direct way of mapping a code point to a string (like chr in Perl and several other languages), but it does have sprintf, which has a %c specifier in the format, which does exactly that job. With AWK, sprintf works exactly the same as its C counterpart.

Find the complete program on GitHub.

Bash

First, getting the option:

while getopts "s:" name
do  if [ "$name" = "s" ]
    then shift=$OPTARG
    fi
done

Bash (and the Bourne Shell) has a getopts buildin, which parses command line options. Here, we’re using the format "s:" to indicate we’re looking for a -s parameter with a mandatory option. We can iterate over the results of getopts, which sets the option in a variable $name in each iteration. The value belonging to that option is in $OPTARG.

To shift the characters, we use a different algorithm that we’re using in the other languages. Bash doesn’t have easy methods to get the code point of a character, or to get the character given a code point. So, we’re using tr to shift the text one character to the left — and we do this as often as needed:

while read line
do    for ((i = 0; i < $shift; i ++))
      do line=`echo $line | tr A-Z ZA-Y`
      done
      echo $line
done

Find the complete program on GitHub.

C

C has a getopt function, which like the Bash buildin, can be iterated over. It takes the same format string as we used in Bash, "s:", indicating a -s parameter taking an option. It take two further arguments, a number argc and an array of strings argv; the first is the length of the array argc, and argv contains the name of the program as the first element, and the arguments to the program as subsequent elements. Typically, argc and argv are just copied from the parameters to main ():

int main (int argc, char ** argv) {
    int     ch;
    int     shift   = -1;
    int     NR_OF_LETTERS = 26;

    while ((ch = getopt (argc, argv, "s:")) != -1) {
        switch (ch) {
            case 's':
                shift = atoi (optarg) % NR_OF_LETTERS;
                break;
        }
    }
    if (shift < 0) {
        fprintf (stderr, "Requires an -s parameter\n");
        exit (1);
    }

C doesn’t automagically convert between integers and strings, so we’re calling atoi on the -s argument; atoi takes a string, and returns an integer.

In C, strings are just arrays of numbers, so shifting is easy. We can directly modify the numeric value in the array. We’re using getline to read lines from standard input. We’re then using a moving pointer, line_ptr to look (and modify) each character in the string line. If line_ptr points to a capital letter — which we check using the function isupper, we shift the character the required amount:

    while ((strlen = getline (&line, &len, stdin)) != -1) {
        char * line_ptr = line;
        while (* line_ptr) {
            if (isupper (* line_ptr)) {
                * line_ptr -= shift;
                if (* line_ptr < 'A') {
                    * line_ptr += NR_OF_LETTERS;
                }
            }
            line_ptr ++;
        }
        printf ("%s", line);
    }

Find the complete program on GitHub.

Lua

In our Lua solution, we’re parsing the command line arguments ourselves. The array with arguments is called arg, and prefixing an array with # gives its size:

local NR_OF_LETTERS =  26
local shift         = -1
if   #arg == 2 and arg [1] == "-s"
then shift = arg [2] % NR_OF_LETTERS
end

if shift < 0
then io . stderr : write ("Requires a '-s SHIFT' option\n")
     os . exit (1)
end

We define a function to shift a capital character; it takes two arguments, char, the character to be shifted, and shift, the distance to shift:

local ORD_A = string . byte ("A")
function do_shift (char, shift)
    local n = string . byte (char) - shift
    if   n < ORD_A
    then n = n + NR_OF_LETTERS
    end
    return string . char (n)
end

Note the byte and char functions from the string class. The first returns the code point of the character passed in; the latter does the reverse, returning the character of the code point passed in.

We then iterate over the input, and use the gsub method to shift capital letters. gsub does a global substitution, and can take a function as parameter. If so, for each match, the function is called, and its return value is used as the substitution part. This is more or less equivalent to Perls s///e.

for line in io . lines () do
    io . write (string . gsub (line, "[A-Z]", function (ch)
             return do_shift (ch, shift)
         end), "\n")
end

Find the complete program on GitHub.

Node.js

Node.js has an impressive module to parse command line arguments, yargs, which we will be using the parse the command line:

const argv = require ('yargs')
. option ('s', {
    type: 'number',
  })
. demandOption ('s')
. argv;

const shift = argv . s

A function to shift an individual character:

const NR_OF_LETTERS = 26
const ORD_A         = "A" . charCodeAt (0)
function shift_char (char, shift) {
    if (char . match (/[A-Z]/)) {
        let n = char . charCodeAt (0) - (shift % NR_OF_LETTERS)
        if (n < ORD_A) {
           n = n + NR_OF_LETTERS
        }
        return String . fromCharCode (n)
    }
    else {
        return char
    }
}

Note that here, unlike with our Lua solution, the function is called for any character in the input, so we need an additional check so we only shift capital letters. To get the code point of a character, we call charCodeAt; to get the character given a code point, we use fromCharCode.

To process the input, we split each line into individual characters, call shift_char on each of them, then join the return values back to a string:

require ('readline')
. createInterface ({input: process . stdin})   
. on ('line', _ => 
       console . log (_ . split ("")
                        . map   (_ => shift_char (_, shift))
                        . join  ("")))

Find the complete program on GitHub.

Python

Python has a getopt module which has a getopt function which is similar to the one used in C and Bash. (Python also has argparse, but we stick with something we’re more familiar with).

NR_OF_LETTERS = 26

shift = -1
opts, args = getopt . getopt (sys . argv [1:], 's:')
for opt, val in opts:
    if opt == "-s":
        shift = int (val) % NR_OF_LETTERS

sys . argv [1:] = []

if shift < 0:
    sys . stderr . write ("Argument -s SHIFT is required\n")
    sys . exit (1)

Note the assignment sys . argv [1:] = [], this clears the argv array. That way, we will be reading from standard input instead of Python trying to open a file called -s.

Strings are immutable in Python, so we will be construction a new string, with the capital letters shifted:

for line in fileinput . input ():
    out = ""
    for i in range (len (line)):
        if "A" <= line [i] <= "Z":
            n = ord (line [i]) - shift
            if n < ORD_A:
                n = n + NR_OF_LETTERS
            out += chr (n)
        else:
            out += line [i]
    sys . stdout . write (out)

Find the complete program on GitHub.

Ruby

Ruby has the optparse module to parse command line parameters. This supplies a method getopts which returns an hash with options and their values.

require 'optparse'
NR_OF_LETTERS = 26

params = ARGV . getopts ('s:')
shift  = params ["s"] ? params ["s"] . to_i % NR_OF_LETTERS : -1

if shift < 0
    STDERR . puts "Requires a -s SHIFT option"
    exit 1
end

We’re using to_i to make the parameter a string.

A function to shift a capital letter:

def shift_letter (letter, shift)
    n = letter . ord - shift
    if   n < 'A' . ord
    then n = n + NR_OF_LETTERS
    end
    return n . chr
end

We’re iterating over the input, and are using gsub to replace each capital letter with a shifted one:

ARGF . each_line do |line|
     line = line . gsub (/[A-Z]/) {|_| shift_letter _, shift}
     puts line
end

Find the complete program on GitHub.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s