Perl Weekly Challenge 97.

My solutions (task 1 and task 2) to the The Weekly Challenge - 097.

Task 1: Caesar Cipher

Submitted by: Mohammad S Anwar

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.

Example

Input: $S = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", $N = 3
Output: "QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD"

Plain:    ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:   XYZABCDEFGHIJKLMNOPQRSTUVW

Plaintext:  THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Ciphertext: QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

I obtain the arguments from @ARGV; the first argument is the string to encode and the second is the displacement which must be numerical.

# Perl weekly challenge 097
# Task 1: Caesar Cipher
#
# See https://wlmb.github.io/2021/01/25/PWC097/#task-1-caesar-cipher
use warnings;
use strict;
use v5.12;
use Scalar::Util qw(looks_like_number);

sub usage {
    say <<END;
    Usage:
	./ch-1.pl S N
    to encode string S using Casear's cipher with displacement N
END
    exit 1;
}

usage() unless @ARGV==2;
my $string = uc shift @ARGV; # Allow lower case but convert to uppercase
my $displacement=shift @ARGV;
usage() unless looks_like_number($displacement);

I build a translation table from the set of allowed characters. Note the sign of the $displacement.

my @plain="A".."Z";
my %translation_of=map {($plain[$_]=>$plain[($_-$displacement)%@plain])} 0..@plain-1;

Finally, I apply the translation table to print the results. I keep untranslated characters unmodified, so that punctuation and spaces go through.

my $translated=join '', map {$translation_of{$_}//$_} split '', $string;
say "Input:  \"$string\" $displacement\nOutput: \"$translated\"\n";
say "Plain:\t", @plain, "\n",
    "Cipher:\t", join '', @translation_of{sort keys %translation_of}, "\n",
    "Displacement:\t$displacement\n",
    "Plaintext:\t$string\n",
    "Ciphertext:\t$translated",

Results:

./ch-1.pl "The quick brown fox jumps over the lazy dog" 3

Results:

Input: "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG" 3
Output: "QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD"

Plain:	ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:	XYZABCDEFGHIJKLMNOPQRSTUVW
Displacement:	3
Plaintext:	THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Ciphertext:	QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

Check that the degenerate case works.

./ch-1.pl "" 3

Results:

Input:  "" 3
Output: ""

Plain:	ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:	XYZABCDEFGHIJKLMNOPQRSTUVW
Displacement:	3
Plaintext:
Ciphertext:

Check that negative displacements work.

./ch-1.pl "The quick brown fox jumps over the lazy dog" -23

Results:

Input:  "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG" -23
Output: "QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD"

Plain:	ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:	XYZABCDEFGHIJKLMNOPQRSTUVW
Displacement:	-23
Plaintext:	THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Ciphertext:	QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

Task 2: Binary Substrings

Submitted by: Mohammad S Anwar

You are given a binary string $B and an integer $S.

Write a script to split the binary string $B of size $S and then find the minimum number of flips required to make it all the same.

Example 1:

Input: $B = “101100101”, $S = 3
Output: 1

Binary Substrings:
    "101": 0 flip
    "100": 1 flip to make it "101"
    "101": 0 flip

Example 2:

Input $B = “10110111”, $S = 4
Output: 2

Binary Substrings:
    "1011": 0 flip
    "0111": 2 flips to make it "1011"

Receive the two arguments in @ARGV.

# Perl weekly challenge 097
# Task 1: Binary substrings
#
# See https://wlmb.github.io/2021/01/25/PWC097/#task-2-binary-substrings
use warnings;
use strict;
use v5.12;

use List::Util qw(all reduce);
use Scalar::Util qw(looks_like_number);
use Memoize; # Just for fun
# Check arguments
usage() unless @ARGV==2;
my $string = shift @ARGV;
my $length=shift @ARGV;
usage() unless looks_like_number($length) && $length>=1;

Check that the string contains only 1’s and 0’s.

my %binary=("0"=>1,"1"=>1); # Binary characters
usage() unless all {$binary{$_}} split '', $string;

Split into substrings using split and keeping only substrings of the correct length, i.e., throwaway the last substring if incomplete.

my @substrings=grep {length $_ == $length} split /(\d{$length})/, $string;

Choose the best target substring to minimize flips.

memoize('cost'); # Don't duplicate effort
my @total_costs=map {total_cost($substrings[$_], @substrings)} 0..@substrings-1;
my $best_index=reduce {$total_costs[$a]<=$total_costs[$b]?$a:$b} 0..@total_costs-1;
my $target=$substrings[$best_index];
my @costs=map {cost($target, $_)} @substrings;

Finally, print results.

say "Input:\t\"$string\"\t$length\n",
    "Output:\t$total_costs[$best_index]\n\n",
    "Binary substrings\n",
    map {"\"$substrings[$_]\": $costs[$_] flips to convert it to \"$target\"\n"}
        0..@substrings-1;

Calculate the cost to flip a set of strings to the first one

sub total_cost {
    my $first=shift;
    my $cost=0;
    $cost+=cost($first,$_) foreach @_;
    return $cost;
}
sub cost {
    my @first=split '',shift;
    my @second=split '',shift;
    my $cost=0;
    $cost += $first[$_]!=$second[$_]?1:0 foreach 0..@first-1;
    return $cost;
}

Results: Finally, the usage message.

sub usage {
say <<END;
 Usage:
   ./ch-1.pl B S
   to split binary string B into substrings of size S>=1
   and then enumerate changes to make them the same
END
   exit 1;
}

Example 1:

./ch-2.pl 101100101 3

Results:

Input:	"101100101"	3
Output:	1

Binary substrings
"101": 0 flips to convert it to "101"
"100": 1 flips to convert it to "101"
"101": 0 flips to convert it to "101"

Example 2:

./ch-2.pl 10110111 4

Results:

Input:	"10110111"	4
Output:	2

Binary substrings
"1011": 0 flips to convert it to "1011"
"0111": 2 flips to convert it to "1011"

Another couple of examples to check if it actually minimizes flips.

./ch-2.pl 0001101111 2
./ch-2.pl 0001101100 2

Results:

Input:	"0001101111"	2
Output:	4

Binary substrings
"00": 2 flips to convert it to "11"
"01": 1 flips to convert it to "11"
"10": 1 flips to convert it to "11"
"11": 0 flips to convert it to "11"
"11": 0 flips to convert it to "11"

Input:	"0001101100"	2
Output:	4

Binary substrings
"00": 0 flips to convert it to "00"
"01": 1 flips to convert it to "00"
"10": 1 flips to convert it to "00"
"11": 2 flips to convert it to "00"
"00": 0 flips to convert it to "00"
Written on January 25, 2021