bezunyl
bezunyl

Reputation: 61

How can I replace multiple letters at the same time for Caesar cipher?

I'm currently working on a program that encrypts (using Caesar cipher) a user input string by replacing specific letters with custom preset letters. For instance A = R, B = T, C = O, etc.

The current program:

using System;
class Program
{
    static void Main(string[] args)
    {
        String decryptedInput = Console.ReadLine().ToUpper();
        String encryptedOutput = decryptedInput.Replace("A", "R")
                                        .Replace("B", "B")
                                        .Replace("C", "T")
                                        .Replace("D", "O")
                                        .Replace("E", "P")
                                        .Replace("F", "M")
                                        .Replace("G", "Z")
                                        .Replace("H", "S")
                                        .Replace("I", "J")
                                        .Replace("J", "K")
                                        .Replace("K", "I")
                                        .Replace("L", "Y")
                                        .Replace("M", "P")
                                        .Replace("N", "G")
                                        .Replace("O", "L")
                                        .Replace("P", "V")
                                        .Replace("Q", "C")
                                        .Replace("R", "X")
                                        .Replace("S", "N")
                                        .Replace("T", "E")
                                        .Replace("U", "H")
                                        .Replace("V", "F")
                                        .Replace("P", "A")
                                        .Replace("X", "U")
                                        .Replace("Y", "Q")
                                        .Replace("Z", "D");
        Console.WriteLine(encryptedOutput);
        Console.ReadKey();
    }
}

I do get the output, but there are some encryption errors. The problem is, that since the lines of code run after each other, letters that are already converted are converted again.

For example: The letter "A" is encrypted/converted to "R". When the program gets to the letter "R", the letter is encrypted/converted again and ends up being "X", which gets converted to "U" later in the code. This happens to almost every letter and then I end up with an encrypted text, which I can never decrypt.

Is there any way to replace all of the letters at the same time, or would you simply recommend me using another function?

Upvotes: 1

Views: 511

Answers (5)

T.S.
T.S.

Reputation: 19386

Simplest representation of what you want to do is here

var dict = new Dictionary<Char, Char>();
// load dictionary here

var original = "ABC";
var newOne = new StringBuilder();
foreach (var c in original)
    newOne.Append(dict[c]);

return newOne.ToString();

This is pseudo-code. But this is simple enough for you to understand that you need to build new string because strings are immutable

I posted Dictionary<string, string> - it can be searched in reverse. You can use List<T>.

 class ED 
 {
     public Char Enc {get; set;}
     public Char Dec {get; set;}
 }
 var list = new List<ED>();

Now you can be clearer, when you encrypt/decrypt. For encrypt do list.First(c => c.Enc.Equals(inputChar)) and for decrypt list.First(c => c.Dec.Equals(inputChar))

Upvotes: 2

Xiaoy312
Xiaoy312

Reputation: 14477

Like other have mentioned, you replaced some character multiples time. This is likely not the desired behavior:

eg: A -> R -> X -> U

Also, there seems to be a typo around those lines:

.Replace("V", "F")
.Replace("P", "A")   // should this be W->A ?
.Replace("X", "U")

All these may have contributed to your problem.

In order to prevent this, just make a single pass to replace the string:

// build up character mappings for decryption
const string EncryptedLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string DecryptedLetters = "RBTOPMZSJKIYPGLVCXNEHFAUQD";
var decryptionMapping = EncryptedLetters.Zip(DecryptedLetters, Tuple.Create)
    .ToDictionary(x => x.Item1, x => x.Item2);

// decrypt the message
var encryptedMessage = "CTHC";
var decryptedMessage = encryptedMessage
    .Select(x => decryptionMapping[x])
    .Aggregate(new StringBuilder(), (sb, x) => sb.Append(x), sb => sb.ToString());

Upvotes: 2

Michael
Michael

Reputation: 1596

I would do this using a Dictionary and linq. Note that any characters (numbers, spaces, symbols) not in the dictionary will not be converted. When I do the lookup against the Dictionary, notice how I set the output char to the current char value of the foreach. Also, since this is case sensitive, I have to convert the string to uppercase.

public class Program
{

    public static void Main(string[] args)
    {           

     string encryptedOutput = "";
        var decryptedInput = "this is a test string";
        Dictionary<char,char> cipherTable = 
        new Dictionary<char,char>{
            {'A', 'R'},
            {'B', 'B'},
            {'C', 'T'},
            {'D', 'O'},
            {'E', 'P'},
            {'F', 'M'},
            {'G', 'Z'},
            {'H', 'S'},
            {'I', 'J'},
            {'J', 'K'},
            {'K', 'I'},
            {'L', 'Y'},
            {'M', 'P'},
            {'N', 'G'},
            {'O', 'L'},
            {'P', 'V'},
            {'Q', 'C'},
            {'R', 'X'},
            {'S', 'N'},
            {'T', 'E'},
            {'U', 'H'},
            {'V', 'F'},
            {'W', 'A'},
            {'X', 'U'},
            {'Y', 'Q'},
            {'Z', 'D'}
        };         
        encryptedOutput = string.Join("",decryptedInput
                                        .ToUpper()
                                        .ToArray()
                                        .Select(c => {char outChar = c; cipherTable.TryGetValue(c, out outChar); return outChar;}));
        Console.WriteLine(encryptedOutput);

    }
}

Upvotes: 0

Nathan Werry
Nathan Werry

Reputation: 886

If you want to keep with your simple replace strategy that you have implemented, instead of doing the dictionary obfuscation that @T.S. demonstrated, you can do the following, your map has 3 distinct groups that needs this treatment, hence the whitespacing.

  1. Sort your map by connections
  2. Break any loops by mapping one to an external value
  3. Process your map backwards
  4. Set step 2 to correct value

Code:

var encryptedOutput = decryptedInput
    .Replace("A", "AA")
    .Replace("P", "A")
    .Replace("M", "P")
    .Replace("F", "M")
    .Replace("V", "F")
    .Replace("P", "V")
    .Replace("E", "P")
    .Replace("T", "E")
    .Replace("C", "T")
    .Replace("Q", "C")
    .Replace("Y", "Q")
    .Replace("L", "Y")
    .Replace("O", "L")
    .Replace("D", "O")
    .Replace("Z", "D")
    .Replace("G", "Z")
    .Replace("N", "G")
    .Replace("S", "N")
    .Replace("H", "S")
    .Replace("U", "H")
    .Replace("X", "U")
    .Replace("R", "X")
    .Replace("AA", "R")

    .Replace("B", "B")

    .Replace("I", "II")
    .Replace("K", "I")
    .Replace("J", "K")
    .Replace("II", "J");

Upvotes: 0

Henk Holterman
Henk Holterman

Reputation: 273494

I will help you with the outside, finding out how it works is a nice exercise in Linq and the rest of the library.

static void Main(string[] args)
{    
    string decryptedInput = "Hello World!";
    string encryptedOutput = new string(decryptedInput.Select(EncryptChar).ToArray());
    Console.WriteLine(encryptedOutput);
}

private static char EncryptChar(char arg)
{
    return arg;
}

Your problem is now reduced to writing a better EncryptChar()

Upvotes: 1

Related Questions