Vigenere 密码同时保持 space 并仅加密以辅音字母 [Java] 开头的单词

Vigenere cipher while maintaining space and encrypt only words starting with consonent letters [Java]

我正在尝试编写一个程序,使用 Vigenere 密码加密一个句子,但只加密以辅音字母开头的单词,同时存储空格。 我对 Java 不是很好,但是我写了一个加密任何给定单词(所有小写字母)的方法我曾认为使用数组在需要的地方打印空格并忽略以元音开头的单词就足够了,但是这样做实际上是在打印数组的第二个单词时给我错误的输出。有人可以指导我做错了什么吗?有没有更好的方法来做到这一点? 到目前为止,这是我的代码:

public class Main {
public static void main(String[] args) {

    Scanner u = new Scanner(System.in);
    final String k = u.next();
    u.nextLine();
    String message = u.nextLine();
    String[] f = message.split(" ");
    System.out.println(encipher(message,k));

    for (String s : f) {
        if (s.charAt(0) == 'a' || s.charAt(0) == 'e' || s.charAt(0) == 'i' ||
                s.charAt(0) == 'o' || s.charAt(0) == 'u') {
            System.out.println(s);
        } else {
            System.out.println(encipher(s, k));
        }

    }
}


public static String encipher(String message, final String key)
{
    String output = "";

    for (int x = 0, y = 0; x < message.length(); x++)
    {
        char c = message.charAt(x);
        if (c < 'a' || c > 'z')
            continue;
        output += (char) ((c + key.charAt(y) - 2 * 'a') % 26 + 'a');
        y = ++y % key.length();
    }

    return output;
}}

可以看出正常输出(忽略空格和vowel/consonant)和数组输出的区别。对于字符串键 obi 和字符串消息“olimpiada brasileira de informatica”,它应该打印“olimpiada psigjtsjzo em informatica”,但在数组中打印“olimpiada psigjtsjzo rf informatica”正确加密]

当逐字应用加密时,每个字都会重置密钥,"de" 使用 "ob":

加密
brasileira
obiobiobio

de
ob

当整个消息被加密时,密钥不会被重置("olimpiada" 有 9 个字母可以被 "obi".length() == 3 整除),并且 "de" 是用 "bi" 加密的:

olimpiada brasileira de informatica
obiobiobi obiobiobio bi obiobiobiob

因此,如果可能需要以某种方式记住 y 索引,或者应该实施另一种方法以在 next 的非空键中提供一个字符索引:

private static Map<String, Integer> keyIndexes = new HashMap<>();

private static char getNextPosition(String key) {
    return key.charAt(keyIndexes.compute(
        key, (k, v) -> v == null ? 0 : (v + 1) % k.length()
    ));
}

private static void resetKey(String key) {
    keyIndexes.remove(key);
}

那么方法 encipher 可以更新为:

public static String encipher(String message, final String key)
{
    StringBuilder output = new StringBuilder();

    for (char c : message.toCharArray())
    {
        if (c < 'a' || c > 'z')
            continue; // skip non-letters

        output.append((char) ((c + getNextPosition(key) - 2 * 'a') % 26 + 'a'));
    }

    return output.toString();
}

测试:

String message = "olimpiada brasileira de informatica";
String k = "obi";
System.out.println(encipher(message, k));
// !!!
resetKey(k);

for (String s : message.split(" ")) {
    if (s.matches("[aeiou]\w*")) { // use regexp to detect a word starting with a vowel
        System.out.println(s);
    } else {
        System.out.println(encipher(s, k));
    }
}

输出:

cmqaqqoeipsigjtsjzoemwoncsuouqqb   // full message, spaces skipped
olimpiada
psigjtsjzo
em
informatica

如果需要将非字母和以元音开头的单词保留为整个消息(不仅仅是单个单词),则应添加几个标志以跟踪当前字符是否在单词内,如果检测到第一个元音:

public static String encipherWork(String message, final String key) {
    StringBuilder output = new StringBuilder();
    
    boolean inWord = false;
    boolean firstVowel = false;

    for (char c : message.toCharArray()) {
        if ('a' <= c && c <= 'z') {
            if (!inWord) {
                firstVowel = (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u');
                inWord = true;
            }
            if (!firstVowel) {
                output.append((char) ((c + getNextPosition(key) - 2 * 'a') % 26 + 'a'));
                continue;
            }
        } else {
            inWord = false;
            firstVowel = false;
        }
        output.append(c); // print non-enciphered characters
    }

    return output.toString();
}

此方法的输出:

olimpiada psigjtsjzo em informatica // full message
olimpiada
psigjtsjzo
em
informatica