← Writing

Java New ID Recommendation: Normalization Order and Truncation

ID normalization depends on transformation order. In particular, remove a trailing dot again after truncating to 15 characters. Removing the original trailing dot earlier does not prevent truncation from exposing another one.

The exercise’s rules normalize ASCII letter case, retain allowed characters, then handle dots, empty results, and length in order. Both solutions assume the non-null input guaranteed by the exercise. The Optional solution uses Java 11’s repeat(), and both compile for Java 11. Save either solution as Solution.java, rather than putting both classes in one file.

Processing flow

The diagram follows the Optional version. The loop version combines filtering and dot collapsing while preserving the same rules.

The diagram follows the Optional version. The loop version combines filtering and dot collapsing while preserving the same rules.

Connect the steps with 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 avoids making lowercase conversion depend on the default locale. The allowed-character pattern retains only lowercase ASCII letters, digits, dots, underscores, and hyphens. Collapse dot runs and remove edge dots. Replace an empty result with a, truncate to 15 characters, remove the newly exposed trailing dot, then repeat the last character until length reaches 3.

Optional is a way to connect transformations, not a required data structure. Optional.of rejects null rather than silently treating it as an empty ID.

Apply the same rules with a loop

This solution combines allowed-character filtering and consecutive-dot removal in one pass. Converting only ASCII uppercase letters uses the exercise’s input range; it is not general Unicode name normalization.

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();
    }
}
Input Result Rule checked
=.= aaa Empty-result fallback and padding
z-+.^. z-- Allowed characters and trailing-dot removal
I iii Lowercasing independent of default locale
abcdefghijklmn.op abcdefghijklmn Removing a dot exposed by truncation

Both solutions take O(n) time in input length n and use O(n) additional space. The regex version may allocate several intermediate strings, while the loop uses a StringBuilder. Without measurements, this does not establish that one is always faster. Consult the String API for replacement, lowercase conversion, and repetition behavior.

For literal digit-word replacements, see replace versus replaceAll. For locale-independent cache keys, see the LRU cache example.

Discussion