Nils
Nils

Reputation: 23

Substituting characters with other characters

I want the decipher a certain text. When I encounter a letter in the string, I want to substitute it with the next letter in the alphabet. Then in the next round I want a substitution with the second next letter.

a -> b

k -> l

Then in the second round it should try

a -> c

k -> m

The problem with characters is that it includes all kinds of strange characters, and when you encounter a z and try to substitute it, it gets substituted with a strange symbol.

Anyone who can help me make some sort of circular list from A-Z? In the following snippet I create a list which contains a to z. In the loop a character is picked, it's index is searched and 'adjusted' and the new character should be retreived. But it doesn't work. (one reason the list being not circular)

public static void decipher(){
    String cyphertext = "aVaqrprzoreoeratraWhyvhfraJnygreUbyynaqreqrgjrroebrefinaRqvguZnetbganneNzfgreqnzNaarjvytenntzrrxbzraznnezbrgabtrrarragvwqwrovwbznoyvwiraBznmnyurgzbrvyvwxuroorabzNaarabtrracnnejrxraqnnegrubhqrafpuevwsgRqvguSenaxvarraoevrsnnaTregehqAnhznaauhaiebrtrerohhezrvfwrvaSenaxshegnzZnva";
    char[] plaintext = new char[cyphertext.length()];

    List<Character> a2z = new ArrayList<Character>(26);
    for (char c = 'A'; c <= 'Z'; c++){
        a2z.add(Character.valueOf(c));
    }

    for(int i = 1; i < 26; i++){
        for(int j = 0; j < cyphertext.length(); j++){
            char currentChar = cyphertext.charAt(j);
            int newCharIndex = a2z.indexOf(currentChar)+i;
            plaintext[j] = a2z.get(newCharIndex);
        }
    }
}

Upvotes: 0

Views: 119

Answers (3)

Andreas
Andreas

Reputation: 159096

If I understand you right, you want the substitution cipher to shift letters up by N, wrapping from Z to A, doing both upper- and lower-case letters, and leave all other characters unchanged.

E.g. if N is 2:

a → c  b → d  ...  x → z  y → a  z → b
A → C  B → D  ...  X → Z  Y → A  Z → B

Like this?

char[] text = "aVaqrprzoreoeratraWhyvhfraJnygreUbyynaqreqrgjrroebrefinaRqvguZnetbganneNzfgreqnzNaarjvytenntzrrxbzraznnezbrgabtrrarragvwqwrovwbznoyvwiraBznmnyurgzbrvyvwxuroorabzNaarabtrracnnejrxraqnnegrubhqrafpuevwsgRqvguSenaxvarraoevrsnnaTregehqAnhznaauhaiebrtrerohhezrvfwrvaSenaxshegnzZnva".toCharArray();
for (int n = 1; n < 26; n++) {
    for (int i = 0; i < text.length; i++) {
        char c = text[i];
        if (c >= 'A' && c <= 'Z')
            text[i] = (char)('A' + (c - 'A' + n) % 26);
        else if (c >= 'a' && c <= 'z')
            text[i] = (char)('a' + (c - 'a' + n) % 26);
    }
}
System.out.println(new String(text));

Output

nIndecemberbrengenJuliusenWalterHollanderdetweebroersvanEdithMargotnaarAmsterdamAnnewilgraagmeekomenmaarmoetnogeeneentijdjebijomablijvenOmazalhetmoeilijkhebbenomAnnenogeenpaarwekendaartehoudenschrijftEdithFrankineenbriefaanGertrudNaumannhunvroegerebuurmeisjeinFrankfurtamMain

Of course, you do realize that shifting by 1, then by 2, then by 3, ... and finally by 26, is the same as shifting once by (1 + 2 + 3 + ... + 26), which is 351, and 351 % 26 = 13.

And a shift by 13 means that encoding and decoding is the same operation.


Explanation

If you shift by 26, you're shifting exactly a full round, i.e. a → a b → b ..., so that's the same as not shifting, hence any shift of N >= 26 is the same as N % 26, e.g. N = 28 is the same as N = 2.

A shift by 1 a → b then by 2 b → d then by 3 d → g, is the same as a shift by 1+2+3=6 a → g. So a shift by 1+2+...+26=351 is the same as a shift by 351 % 26 = 13.

If variable c is an uppercase letter (A-Z), then c - 'A' is a number between 0 and 25. Adding an example shift of 6, you get a number 6-31. Doing remainder by 26 (% 26) gives you 6-25,0-5 and then + 'A' gives you G-Z,A-F, meaning A → G B → H ... Y → E Z → F, each letter shifted by 6.

Same for lowercase letters.

Upvotes: 1

Nir Alfasi
Nir Alfasi

Reputation: 53525

You don't need a circular list to handle the edge cases. Further, you should keep in mind that characters A-Z have ascii values between 65-90 and a-z between 97-122. You could create two circular lists, but they're not needed because the edge-cases are easy to handle:

public static void main(String[] args) {
    String cyphertext = "aVaqrprzoreoeratraWhyvhfraJnygreUbyynaqreqrgjrroebrefinaRqvguZnetbganneNzfgreqnzNaarjvytenntzrrxbzraznnezbrgabtrrarragvwqwrovwbznoyvwiraBznmnyurgzbrvyvwxuroorabzNaarabtrracnnejrxraqnnegrubhqrafpuevwsgRqvguSenaxvarraoevrsnnaTregehqAnhznaauhaiebrtrerohhezrvfwrvaSenaxshegnzZnva";
    char[] plaintext = new char[cyphertext.length()];

    // first shift
    shiftLetters(cyphertext, plaintext, 1);
    // update cyphertext with the intermediate result
    cyphertext = new String(plaintext);
    // second shift
    shiftLetters(cyphertext, plaintext, 2);
    // print result
    System.out.println(new String(plaintext));

}

private static void shiftLetters(String cyphertext, char[] plaintext, int shifts) {
    for (int i=0; i<cyphertext.length(); i++){
        int tmp = cyphertext.charAt(i) + shifts;
        tmp = handleEdgeCases(tmp);
        plaintext[i] = (char)(tmp);
    }
}

// here we handle the "circular" cases
private static int handleEdgeCases(int tmp) {
    if (tmp > 90 && tmp < 97) {
        tmp = tmp - 90 + 65;
    } else if (tmp > 122) {
        tmp = tmp - 122 + 97;
    }
    return tmp;
}

Upvotes: 1

Anubian Noob
Anubian Noob

Reputation: 13596

The problem is here:

int newCharIndex = a2z.indexOf(currentChar)+i;
plaintext[j] = a2z.get(newCharIndex);

The decoded character here is between 0 and 25. Ascii's A-Z goes from 65 to 90. You want to move the index in a2z to the range from A to Z:

plaintext[j] = a2z.get(newCharIndex) + 'A';

Upvotes: 1

Related Questions