Программа Caesar Cipher Java не может сдвигаться более чем на 23

Я создаю программу для шифра Цезаря, которая сдвигает буквы в слове один раз, когда я нажимаю клавишу ввода, и предлагает пользователю снова сдвинуть или выйти.

Он работает, пока я не доберусь до 23 смен, затем по какой-то причине он начинает использовать небуквенные символы, и я не уверен, почему это происходит.

Какие-либо предложения? Вот код:

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class Cipher {

    public static void main(String[] args) {

        // encrypted text
        String ciphertext;

        // input from keyboard
        Scanner keyboard = new Scanner(System.in);

        if (args.length > 0) {
            ciphertext = "";
            try {
                Scanner inputFile = new Scanner(new File(args[0]));
                while (inputFile.hasNext())
                    ciphertext += inputFile.nextLine();
            } catch (IOException ioe) {
                System.out.println("File not found: " + args[0]);
                System.exit(-1);
            }
        } else {
            System.out.print("Please enter text--> ");
            ciphertext = keyboard.nextLine();
        }

        // -----------------------------------------------------------------

        int distance = 0;  // how far the ciphertext should be shifted
        String next = "";  // user input after viewing
        while (!next.equals("quit")) {
            String plaintext = "";
            distance += 1;
            for (int i = 0; i < ciphertext.length(); i++) {
                char shift = ciphertext.charAt(i);
                if (Character.isLetter(shift)) {
                    shift = (char) (ciphertext.charAt(i) - distance);
                    if (Character.isUpperCase(ciphertext.charAt(i))) {
                        if (shift > '0' && shift < 'A') {
                            shift = (char) (shift + 26);
                            plaintext += shift;
                        } else {
                            plaintext += shift;
                        }
                    }
                    if (Character.isLowerCase(ciphertext.charAt(i))) {
                        if (shift > '0' && shift < 'a' && ciphertext.charAt(i) < 't') {
                            shift = (char) (shift + 26);
                            plaintext += shift;
                        } else {
                            plaintext += shift;
                        }
                    }
                } else {
                    plaintext += shift;
                }
            }

            System.out.println(ciphertext);

            // At this point, plaintext is the shifted ciphertext.
            System.out.println("distance " + distance);
            System.out.println(plaintext);
            System.out.println("Press enter to see the next option,"
                    + "type 'quit' to quit.");
            next = keyboard.nextLine().trim();
        }
        System.out.println("Final shift distance was " + distance + " places");
    }
}

person Isaiah    schedule 13.02.2017    source источник
comment
Вы отлаживали свой код?   -  person Jeroen Heier    schedule 13.02.2017
comment
...plaintext += shift; } else { plaintext += shift; } — Это не имеет смысла. Вы можете поместить оператор plaintext += shift вне оператора else.   -  person MC Emperor    schedule 14.02.2017
comment
Просто чтобы вы знали, прошлые 'Z' и 'z' - это несколько символов, которые не имеют ничего общего с буквами. Вы захотите их пропустить.   -  person Makoto    schedule 16.02.2017
comment
@Isaiah Если пользователь ответил на ваш вопрос, примите также его ответ (Принятие ответов: как это работает?). Если нет, пожалуйста, укажите, что осталось без ответа, это действительно важная часть StackOverflow, большое спасибо.   -  person Zabuzard    schedule 24.09.2017


Ответы (1)


Как работает сдвиг в вашем методе? Что ж, он использует тот факт, что char в Java также можно рассматривать как int, простое число.

Из-за этого вы можете делать такие вещи:

char c = 'A';                                 // Would print: A
int cAsValue = (int) c;                       // Would print: 65
int nextValue = cAsValue + 1;                 // Would print: 66
char nextValueAsCharacter = (char) nextValue; // Would print: B

или даже так:

int first = (int) 'A';                // Would print: 65
int second = (int) 'D';               // Would print: 68
int third = first + second;           // Would print: 133
char thirdAsCharacter = (char) third; // Would not print anything meaningful

Хорошо, теперь, когда мы знаем, как мы можем интерпретировать char как int, давайте проанализируем, почему 65 представляет символ A и почему 133 не имеет никакого значения.

Ключевое слово здесь UTF-16. Символы в Java закодированы в UTF-16, и есть таблицы, в которых перечислены все символы этой кодировки с их конкретным десятичным числом, например здесь .

Вот соответствующий отрывок:

Таблица UTF-16, показывающая символы вокруг 'A'

Это объясняет, почему 65 представляет A и почему 133 не имеет смысла.


Причина, по которой вы получаете странные результаты после некоторых сдвигов, заключается в том, что размер алфавита составляет всего 26 символов.

Я думаю, вы ожидаете, что все начнется сначала, и a, смещенное на 26, снова станет a. Но, к сожалению, ваш код недостаточно умен, он просто берет текущий символ и добавляет к нему сдвиг, например:

char current = 'a';
int shift = 26;

int currentAsInt = (int) current;        // Would print: 97
int shifted = currentAsInt + shift;      // Would print: 123
char currentAfterShift = (char) shifted; // Would print: {

Сравните это с соответствующей частью таблицы:

Таблица UTF-16, показывающая символы вокруг '{'

Так что после z снова не будет a, а скорее {.


Итак, после того, как загадка была раскрыта, давайте теперь поговорим о том, как ее исправить и сделать ваш код умнее.

Вы можете просто проверить границы, например: "если оно больше, чем значение для 'z' или меньше, чем 'a', то снова вернуть его в правильный диапазон". Мы можем сделать это легко, используя оператор по модулю, заданный %. Он делит одно число на другое и возвращает остаток от деления.

Вот как мы можем его использовать:

char current = 'w';
int shift = 100;
int alphabetSize = 26; // Or alternatively ('z' - 'a')

int currentAsInt = (int) current;          // Would print: 119
int shiftInRange = shift % alphabetSize;   // Would print: 22
int shifted = currentAsInt + shiftInRange; // Would print: 141 (nothing meaningful)

// If exceeding the range then begin at 'a' again
int shiftCorrected = shifted;
if (shifted > 'z') {
    shiftCorrected -= alphabetSize; // Would print: 115
}

char currentAfterShift = (char) shiftCorrected; // Would print: s 

Таким образом, вместо сдвига на 100 мы смещаем только соответствующую часть, 22. Представьте, что персонаж проходит три круга по всему алфавиту, потому что 100 / 26 ~ 3.85. После этих трех раундов мы переходим к оставшимся 0.85 раундам, то есть 22 шагам, остатку после деления 100 на 26. Именно это и сделал для нас оператор %.

Пройдя эти 22 шагов, мы все еще могли превысить лимит, но не более чем на один раунд. Мы исправляем это, вычитая размер алфавита. Таким образом, вместо 22 шагов мы делаем 22 - 26 = -4 шагов, что эмулирует «прохождение 4 шагов до конца алфавита, затем снова начало с «a» и, наконец, прохождение 18 шагов до «s»».

person Zabuzard    schedule 16.08.2017