九宫格输入法模拟

356 阅读6分钟

九宫格输入法模拟

问题背景

我们需要模拟一个经典的手机九宫格输入法。该输入法包含数字英文两种输入模式,用户通过按下一系列按键(包括数字键、模式切换键和停顿键)来输入一个字符串。

输入模式与状态

  1. 数字模式 (Numeric Mode) :

    • 这是输入法的初始默认状态
    • 在此模式下,按下任一数字键 0-9,会直接输出对应的数字。例如,按 1 输出 1
  2. 英文模式 (English Mode) :

    • 通过特定按键(#)可以切换到此模式。
    • 在此模式下,按下数字键会根据其对应的英文字符集进行输入。

按键映射与规则 (英文模式下)

按键对应的字符(循环)
0``(空格)
1. , ? !
2a b c
3d e f
4g h i
5j k l
6m n o
7p q r s
8t u v
9w x y z
  • 连续按键: 连续、快速地按同一个数字键,会依次选择其对应的字符。例如,在英文模式下,按一下 2 选择 a,再按一下选择 b,第三下选择 c,第四下则循环回到 a

  • 字符上屏 (Commit) : 当一个字符被选中后,在以下三种情况下,该字符会被最终确认并“上屏”(添加到结果字符串中):

    1. 按下了不同的按键: 例如,先按了 22 (选中b),紧接着按了 3,此时 b 会被上屏。
    2. 按下了停顿符 ! : 例如,22!b 会被上屏。
    3. 输入结束:整个按键序列处理完毕时,最后一个被选中的字符会自动上屏。

输入指令

  • 0-9: 数字键,其行为取决于当前模式。
  • #: 模式切换键。每次按下,就在“数字模式”和“英文模式”之间切换一次。
  • !: 停顿符。在英文模式下,用于中断连续按键并上屏当前选中的字符。

任务要求

给定一个表示用户按键操作的字符串 inputStr,请模拟输入法的完整行为,并输出最终显示在屏幕上的字符串。


输入格式

  • inputStr: 一个字符串,代表用户的系列按键操作。

    • 1 <= inputStr.length <= 200
    • 字符串仅由 0-9#! 组成。

输出格式

  • 一个字符串,表示模拟输入法后实际显示出来的内容。

样例说明

样例 1

  • 输入: "123"

  • 输出: "123"

  • 解释:

    1. 初始状态: 数字模式。
    2. 按下 1 -> 输出 1
    3. 按下 2 -> 输出 2
    4. 按下 3 -> 输出 3
    5. 最终结果为 "123"

样例 2

  • 输入: "#22!23044444411"

  • 输出: "bad i."

  • 解释:

    1. #: 切换到 英文模式
    2. 22: 连续按键。第1下选中 a,第2下选中 b
    3. !: 停顿。将当前选中的 b 上屏。输出: "b"
    4. 2: 按一下 2,选中 a
    5. 3: 按下了不同的键。将之前选中的 a 上屏。然后处理 3,选中 d输出: "ba"
    6. 0: 按下了不同的键。将之前选中的 d 上屏。然后处理 0,选中 ``(空格)。输出: "bad"
    7. 4...4 (共6次): 按下了不同的键。将之前选中的 ``上屏。然后处理 4。连续按6次,g->h->i->g->h->i。最终选中 i输出: "bad "
    8. 11: 按下了不同的键。将之前选中的 i 上屏。然后处理 1。连续按2次,.->,。最终选中 ,输出: "bad i"
    9. 输入结束: 将最后选中的字符 , 上屏。 最终输出: "bad i,"
import java.util.Scanner;

/**
 * 解决“手机九宫格输入法”问题的方案类。
 */
public class Solution {

    // --- Keypad Mapping ---
    // 使用一个静态常量数组来映射数字键到其对应的字符序列。
    // 索引 i 对应数字键 'i'。
    // 样例中 "11" -> ".",说明 '1' 键的映射包含多个字符,这里采用一种常见的旧手机布局。
    private static final String[] KEY_MAP = {
        " ",      // 0
        ".,?!",   // 1
        "abc",    // 2
        "def",    // 3
        "ghi",    // 4
        "jkl",    // 5
        "mno",    // 6
        "pqrs",   // 7
        "tuv",    // 8
        "wxyz"    // 9
    };

    // --- State Variables ---
    // 将这些状态作为类的成员变量,方便在多个方法中共享和修改。
    private StringBuilder resultBuilder; // 用于构建最终输出的字符串
    private char lastPressedKey;         // 英文模式下,上一个按下的数字键
    private int pressCount;              // 对应 lastPressedKey 的连续按键次数

    /**
     * 主逻辑方法,模拟输入过程并返回最终显示的字符串。
     *
     * @param inputStr 包含所有操作的命令字符串。
     * @return 实际显示出来的字符串。
     */
    public String simulateInput(String inputStr) {
        // --- 1. 初始化状态 ---
        this.resultBuilder = new StringBuilder();
        this.lastPressedKey = '\0'; // 使用空字符 '\0' 表示没有正在处理的按键序列
        this.pressCount = 0;
        boolean isDigitMode = true; // 初始默认为「数字状态」

        // --- 2. 遍历命令字符串 ---
        for (char cmd : inputStr.toCharArray()) {
            if (cmd == '#') { // 模式切换
                // 在切换模式前,必须先将当前正在输入的字母“确定”下来
                finalizeLetter();
                isDigitMode = !isDigitMode; // 切换模式
            } else if (cmd == '!') { // 停顿
                // 停顿的作用就是中断连续按键,确定当前字母
                finalizeLetter();
            } else if (Character.isDigit(cmd)) { // 按下数字键
                if (isDigitMode) {
                    // --- 数字状态 ---
                    // 在数字状态下,任何数字键的按下都会中断之前的字母输入序列
                    finalizeLetter();
                    // 直接追加数字字符
                    resultBuilder.append(cmd);
                } else {
                    // --- 英文状态 ---
                    // 如果当前按键与上一个相同
                    if (cmd == lastPressedKey) {
                        pressCount++; // 增加连续按键计数
                    } else {
                        // 如果按键不同,说明上一个字母序列结束了
                        finalizeLetter(); // 先“确定”并输出上一个字母
                        // 然后开始一个新的字母序列
                        lastPressedKey = cmd;
                        pressCount = 1;
                    }
                }
            }
        }

        // --- 3. 最终处理 ---
        // 循环结束后,如果最后的操作是一串连续的字母按键,它们尚未被“确定”
        // 需要在这里进行最后一次处理
        finalizeLetter();

        // 返回构建好的完整字符串
        return resultBuilder.toString();
    }

    /**
     * 辅助方法:确定并输出当前正在处理的英文字符。
     * 在按键序列被中断时(如按下不同键、'#'、'!' 或输入结束)调用。
     */
    private void finalizeLetter() {
        // 如果 pressCount 为 0,说明没有待处理的按键序列
        if (pressCount == 0) {
            return;
        }

        // 1. 根据按下的键,从 KEY_MAP 中获取候选字符字符串
        int keyIndex = lastPressedKey - '0'; // '2' -> 2
        String candidates = KEY_MAP[keyIndex];

        // 2. 计算最终选择哪个字符
        // (pressCount - 1) 是因为按1次对应索引0,按2次对应索引1...
        // % candidates.length() 处理循环选择,例如按4次'2'键,(4-1)%3=0,回到'a'
        int charIndex = (pressCount - 1) % candidates.length();

        // 3. 将选定的字符追加到结果中
        resultBuilder.append(candidates.charAt(charIndex));

        // 4. 重置状态,为下一个按键序列做准备
        lastPressedKey = '\0';
        pressCount = 0;
    }
}

/**
 * 用于处理 ACM 风格输入输出的主类。
 */
class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String inputStr = scanner.nextLine(); // 读取指令字符串
        scanner.close();

        // 创建 Solution 类的实例并调用方法
        Solution solution = new Solution();
        String result = solution.simulateInput(inputStr);

        // 输出最终结果
        System.out.println(result);
    }
}