← 开发笔记

Java 新 ID 推荐:规范化顺序与截断

ID 规范化依赖转换顺序。尤其是在截断到 15 个字符后,必须再次删除结尾的点。提前删除原有的结尾点,并不能阻止截断暴露出另一个结尾点。

题目规则依次规范化 ASCII 字母大小写、保留允许的字符,再处理点、空结果和长度。两种解法都假设输入满足题目保证的非 null 条件。Optional 解法使用 Java 11 的 repeat(),两种解法均可按 Java 11 编译。任选一种保存为 Solution.java,不要把两个类放入同一文件。

处理流程

流程图对应 Optional 版本。循环版本合并过滤与连续点压缩操作,同时保留相同规则。

流程图对应 Optional 版本。循环版本合并过滤与连续点压缩操作,同时保留相同规则。

用 Optional 连接各步骤

import java.util.Locale;
import java.util.Optional;

public class Solution {
    public String solution(String new_id) {
        return Optional.of(new_id)
                .map(s -> s.toLowerCase(Locale.ROOT))
                .map(s -> s.replaceAll("[^a-z0-9._-]", ""))
                .map(s -> s.replaceAll("[.]+", "."))
                .map(s -> s.replaceAll("^[.]|[.]$", ""))
                .map(s -> s.isEmpty() ? "a" : s)
                .map(s -> s.length() > 15 ? s.substring(0, 15) : s)
                .map(s -> s.replaceAll("[.]$", ""))
                .map(s -> s.length() < 3
                        ? s + s.substring(s.length() - 1).repeat(3 - s.length()) : s)
                .orElseThrow();
    }
}

Locale.ROOT 避免小写转换依赖默认区域设置。允许字符的模式只保留小写 ASCII 字母、数字、点、下划线和连字符。合并连续点并删除两端的点。将空结果替换为 a,截断到 15 个字符,删除截断后暴露的结尾点,再重复最后一个字符,直到长度达到 3。

Optional 用于连接转换步骤,并非必需的数据结构。Optional.of 会拒绝 null,而不会默默将其当作空 ID。

用循环应用相同规则

该解法在一次遍历中合并允许字符过滤和连续点删除。只转换 ASCII 大写字母是基于题目的输入范围,并非通用 Unicode 名称规范化。

import java.util.Objects;

public class Solution {
    public String solution(String new_id) {
        Objects.requireNonNull(new_id);
        StringBuilder result = new StringBuilder();
        for (char input : new_id.toCharArray()) {
            char c = input >= 'A' && input <= 'Z' ? (char) (input + 32) : input;
            boolean allowed = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
                    || c == '-' || c == '_' || c == '.';
            if (!allowed || (c == '.' && (result.length() == 0
                    || result.charAt(result.length() - 1) == '.'))) continue;
            result.append(c);
        }
        if (result.length() > 0 && result.charAt(result.length() - 1) == '.') {
            result.setLength(result.length() - 1);
        }
        if (result.length() == 0) result.append('a');
        if (result.length() > 15) result.setLength(15);
        if (result.charAt(result.length() - 1) == '.') result.setLength(result.length() - 1);
        while (result.length() < 3) result.append(result.charAt(result.length() - 1));
        return result.toString();
    }
}
输入 结果 检查的规则
=.= aaa 空结果回退与补齐
z-+.^. z-- 允许字符与结尾点删除
I iii 不依赖默认区域设置的小写转换
abcdefghijklmn.op abcdefghijklmn 删除截断后暴露的点

两种解法相对于输入长度 n 的时间复杂度均为 O(n),额外空间为 O(n)。正则版本可能分配多个中间字符串,循环版本则使用 StringBuilder。在没有测量数据时,不能由此断定其中一种总是更快。替换、小写转换和重复的行为可参阅 String API。

数字单词的字面量替换可参阅 replace 与 replaceAll。不依赖区域设置的缓存键可参阅 LRU 缓存示例。

讨论