# Masking Personal Information
**Difficulty:** MEDIUM
[External](https://leetcode.com/problems/masking-personal-information)
Canonical: https://scaleengineer.com/dsa/problems/masking-personal-information
**Data structures:** String
**Companies:** [X](https://scaleengineer.com/companies/x)
---
## Problem
You are given a personal information string `s`, representing either an **email address** or a **phone number**. Return _the **masked** personal information using the below rules_.

**Email address:**

An email address is:

* A **name** consisting of uppercase and lowercase English letters, followed by
* The `'@'` symbol, followed by
* The **domain** consisting of uppercase and lowercase English letters with a dot `'.'` somewhere in the middle (not the first or last character).

To mask an email:

* The uppercase letters in the **name** and **domain** must be converted to lowercase letters.
* The middle letters of the **name** (i.e., all but the first and last letters) must be replaced by 5 asterisks `"*****"`.

**Phone number:**

A phone number is formatted as follows:

* The phone number contains 10-13 digits.
* The last 10 digits make up the **local number**.
* The remaining 0-3 digits, in the beginning, make up the **country code**.
* **Separation characters** from the set `{'+', '-', '(', ')', ' '}` separate the above digits in some way.

To mask a phone number:

* Remove all **separation characters**.
* The masked phone number should have the form:  
  * `"***-***-XXXX"` if the country code has 0 digits.
  * `"+*-***-***-XXXX"` if the country code has 1 digit.
  * `"+**-***-***-XXXX"` if the country code has 2 digits.
  * `"+***-***-***-XXXX"` if the country code has 3 digits.
* `"XXXX"` is the last 4 digits of the **local number**.

**Example 1:**

**Input:** s = "LeetCode@LeetCode.com"
**Output:** "l*****e@leetcode.com"
**Explanation:** s is an email address.
The name and domain are converted to lowercase, and the middle of the name is replaced by 5 asterisks.

**Example 2:**

**Input:** s = "AB@qq.com"
**Output:** "a*****b@qq.com"
**Explanation:** s is an email address.
The name and domain are converted to lowercase, and the middle of the name is replaced by 5 asterisks.
Note that even though "ab" is 2 characters, it still must have 5 asterisks in the middle.

**Example 3:**

**Input:** s = "1(234)567-890"
**Output:** "***-***-7890"
**Explanation:** s is a phone number.
There are 10 digits, so the local number is 10 digits and the country code is 0 digits.
Thus, the resulting masked number is "***-***-7890".

**Constraints:**

* `s` is either a **valid** email or a phone number.
* If `s` is an email:  
  * `8 <= s.length <= 40`
  * `s` consists of uppercase and lowercase English letters and exactly one `'@'` symbol and `'.'` symbol.
* If `s` is a phone number:  
  * `10 <= s.length <= 20`
  * `s` consists of digits, spaces, and the symbols `'('`, `')'`, `'-'`, and `'+'`.

# Approaches
## String Manipulation with Regex and Concatenation
This approach distinguishes between an email and a phone number by checking for the `'@'` symbol. It then uses standard Java `String` methods for manipulation. For emails, it relies on `toLowerCase()`, `substring()`, and string concatenation (`+`). For phone numbers, it leverages a regular expression with `replaceAll()` to extract the digits before formatting the output.
**Time:** O(N), where N is the length of the input string `s`. Operations like `indexOf()`, `toLowerCase()`, `substring()`, and `replaceAll()` all take time proportional to the string's length. · **Space:** O(N), where N is the length of the input string. This is due to the creation of several intermediate strings during the transformation process (e.g., for `toLowerCase()`, `replaceAll()`, and concatenations).
**Pros:** The code is often more concise and can be easier to read, especially the digit extraction using `replaceAll()`.; It makes good use of the rich, high-level API provided by the standard `String` class.
**Cons:** Creating multiple intermediate `String` objects due to immutability (e.g., from `toLowerCase()`, `substring()`, `replaceAll()`, and `+` concatenation) can be inefficient in terms of both time and memory overhead.; Regular expression processing via `replaceAll()` is generally slower than a simple manual iteration for a task as simple as extracting digits.
### Explanation
The core idea is to use high-level, built-in string functions to perform the masking. First, we find the index of `'@'`. If it exists, we treat the string as an email. We convert it to lowercase, then take the first and last characters of the name part and sandwich `"*****"` between them. The domain part is appended as is. If `'@'` is not found, it's a phone number. We use a regular expression to filter out everything but the digits. Then, based on the count of these digits, we construct the masked number format as specified in the rules, using simple string concatenation to piece together the parts.

```java
class Solution {
    public String maskPII(String s) {
        int atIndex = s.indexOf('@');
        if (atIndex >= 0) { // Email
            s = s.toLowerCase();
            String name = s.substring(0, atIndex);
            String domain = s.substring(atIndex);
            return name.charAt(0) + "*****" + name.charAt(name.length() - 1) + domain;
        } else { // Phone
            String digits = s.replaceAll("[^0-9]", "");
            String local = "***-***-" + digits.substring(digits.length() - 4);
            if (digits.length() == 10) {
                return local;
            }
            String countryCodePrefix = "+";
            for (int i = 0; i < digits.length() - 10; ++i) {
                countryCodePrefix += "*";
            }
            return countryCodePrefix + "-" + local;
        }
    }
}
```
### Algorithm
- Check if the input string `s` contains an `'@'` symbol to determine if it's an email or a phone number.
- **If it's an email**:
  1. Convert the entire string to lowercase using `s.toLowerCase()`.
  2. Find the index of the `'@'` symbol.
  3. Extract the name (part before `'@'`) and the domain (part from `'@'` onwards) using `substring()`.
  4. Construct the masked name by concatenating the first character of the name, the string `"*****"`, and the last character of the name.
  5. Concatenate the masked name with the domain to get the final result.
- **If it's a phone number**:
  1. Use the `replaceAll("[^0-9]", "")` method to strip all non-digit characters from the string, leaving only digits.
  2. Determine the length of the resulting digit string.
  3. Extract the last 4 digits.
  4. Based on the total number of digits (10, 11, 12, or 13), construct the appropriate prefix using string concatenation.
  5. Combine the prefix and the last 4 digits to form the final masked phone number.

## Optimized String Building with StringBuilder
This optimized approach also starts by identifying the input type. However, it uses a `StringBuilder` for all string construction to avoid the performance penalty of creating multiple immutable `String` objects. For phone numbers, it manually iterates through the string to collect digits using `Character.isDigit()`, which is typically faster than invoking a regex engine. This method is more efficient in both time and memory.
**Time:** O(N), where N is the length of the input string `s`. We perform a single pass over the string to process it. `StringBuilder` append operations take amortized O(1) time, leading to an overall linear time complexity with better constant factors than the first approach. · **Space:** O(N), where N is the length of the input string. The `StringBuilder` can grow up to size N in the email case. For phone numbers, the space is effectively O(1) as the number of digits and the final string length are bounded by small constants.
**Pros:** Highly efficient in terms of both time and memory, as it minimizes the creation of temporary `String` objects by using `StringBuilder`.; Manual digit extraction is generally faster than a regex-based approach for this simple filtering task.; Follows best practices for string manipulation in performance-sensitive contexts.
**Cons:** The code can be slightly more verbose compared to using higher-level functions like `replaceAll`.; Requires manual implementation of logic that is available in built-in functions.
### Explanation
This approach focuses on performance by minimizing object creation. Instead of creating new `String` objects for each modification, we use a mutable `StringBuilder`. For emails, we build the masked string piece by piece. For phone numbers, we first loop through the input to build a `StringBuilder` containing only digits. Then, we use this digit information to construct the final formatted and masked number in a second `StringBuilder`. This avoids the overhead of both regular expressions and repeated string concatenations, making it a more performant and memory-friendly solution, which is a best practice in Java.

```java
class Solution {
    public String maskPII(String s) {
        int atIndex = s.indexOf('@');
        if (atIndex >= 0) { // Email
            String lower = s.toLowerCase();
            StringBuilder sb = new StringBuilder();
            sb.append(lower.charAt(0));
            sb.append("*****");
            sb.append(lower.charAt(atIndex - 1));
            sb.append(lower.substring(atIndex));
            return sb.toString();
        } else { // Phone
            StringBuilder digits = new StringBuilder();
            for (char c : s.toCharArray()) {
                if (Character.isDigit(c)) {
                    digits.append(c);
                }
            }
            
            StringBuilder result = new StringBuilder();
            int numDigits = digits.length();
            String lastFour = digits.substring(numDigits - 4);

            if (numDigits > 10) {
                result.append("+");
                for (int i = 0; i < numDigits - 10; i++) {
                    result.append("*");
                }
                result.append("-");
            }
            
            result.append("***-***-");
            result.append(lastFour);
            
            return result.toString();
        }
    }
}
```
### Algorithm
- Check if the input string `s` contains an `'@'` symbol.
- **If it's an email**:
  1. Initialize a `StringBuilder`.
  2. Append the first character of `s`, converted to lowercase.
  3. Append the string `"*****"`.
  4. Append the character before the `'@'`, converted to lowercase.
  5. Append the rest of the string from `'@'` onwards, converted to lowercase.
  6. Return the result by calling `toString()` on the `StringBuilder`.
- **If it's a phone number**:
  1. Initialize a `StringBuilder` to store digits.
  2. Iterate through the input string `s` character by character. If a character is a digit, append it to the digit `StringBuilder`.
  3. Once all digits are collected, determine their count.
  4. Initialize a new `StringBuilder` for the final result.
  5. Construct the masked number by appending the correct prefix (based on digit count) and the last four digits to this result `StringBuilder`.
  6. Return the final string.

# Solutions
### Java

```java
class Solution {
public
  String maskPII(String s) {
    if (Character.isLetter(s.charAt(0))) {
      s = s.toLowerCase();
      int i = s.indexOf('@');
      return s.substring(0, 1) + "*****" + s.substring(i - 1);
    }
    StringBuilder sb = new StringBuilder();
    for (char c : s.toCharArray()) {
      if (Character.isDigit(c)) {
        sb.append(c);
      }
    }
    s = sb.toString();
    int cnt = s.length() - 10;
    String suf = "***-***-" + s.substring(s.length() - 4);
    return cnt == 0 ? suf : "+" + "*".repeat(cnt) + "-" + suf;
  }
}

```

### CPP

```cpp
class Solution {
public:
  string maskPII(string s) {
    int i = s.find('@');
    if (i != -1) {
      string ans;
      ans += tolower(s[0]);
      ans += "*****";
      for (int j = i - 1; j < s.size(); ++j) {
        ans += tolower(s[j]);
      }
      return ans;
    }
    string t;
    for (char c : s) {
      if (isdigit(c)) {
        t += c;
      }
    }
    int cnt = t.size() - 10;
    string suf = "***-***-" + t.substr(t.size() - 4);
    return cnt == 0 ? suf : "+" + string(cnt, '*') + "-" + suf;
  }
};

```

### Python

```python
class Solution:
    def maskPII(self, s: str) -> str: if s[0]. isalpha(): s = s . lower() return s[0] + '*****' + s[s . find('@') - 1:] s = '' . join(c for c in s if c . isdigit()) cnt = len(s) - 10 suf = '***-***-' + s[- 4:] return suf if cnt == 0 else f '+ { "*" * cnt } - { suf } '

```
