# LeetCode 831: Masking Personal Information

## Problem Restatement

We are given a string `s`.

The string represents either:

| Type | Example |
|---|---|
| Email address | `"LeetCode@LeetCode.com"` |
| Phone number | `"1(234)567-890"` |

We need to return the masked version of the personal information.

The masking rules are different for emails and phone numbers.

An email contains the character `"@"`.

A phone number contains digits and separator characters.

## Input and Output

| Item | Meaning |
|---|---|
| Input | A personal information string `s` |
| Output | The masked personal information |
| Email rule | Lowercase all letters, keep first and last character of the name, replace middle with 5 stars |
| Phone rule | Keep only last 4 digits visible |
| Phone digits | A phone number contains 10 to 13 digits |

Function shape:

```python
class Solution:
    def maskPII(self, s: str) -> str:
        ...
```

## Examples

Example 1:

```python
s = "LeetCode@LeetCode.com"
```

Lowercase it:

```python
"leetcode@leetcode.com"
```

The name before `"@"` is:

```python
"leetcode"
```

Keep the first and last character of the name:

```python
"l" and "e"
```

Replace the middle with exactly five stars:

```python
"l*****e@leetcode.com"
```

So the answer is:

```python
"l*****e@leetcode.com"
```

Example 2:

```python
s = "AB@qq.com"
```

Lowercase it:

```python
"ab@qq.com"
```

The name is `"ab"`.

There are no middle characters, but the rule still uses exactly five stars.

So the answer is:

```python
"a*****b@qq.com"
```

Example 3:

```python
s = "1(234)567-890"
```

The digits are:

```python
"1234567890"
```

There are exactly 10 digits, so there is no country code.

Keep only the last 4 digits:

```python
"7890"
```

The answer is:

```python
"***-***-7890"
```

Example 4:

```python
s = "86-(10)12345678"
```

The digits are:

```python
"861012345678"
```

There are 12 digits.

The last 10 digits are the local number.

The first 2 digits are the country code.

So the masked country code has two stars:

```python
"+**-"
```

The answer is:

```python
"+**-***-***-5678"
```

## First Thought: Handle Both Formats Separately

This problem is mostly string formatting.

There are two separate cases:

1. Email address
2. Phone number

We can detect an email by checking whether `"@"` exists in `s`.

If it is an email, lowercase the whole string and build the masked email.

If it is a phone number, extract only digits and build the masked phone string.

## Key Insight

The input is guaranteed valid.

So we do not need to validate whether the email or phone number is legal.

We only need to apply the required formatting.

For email:

```python
first_character + "*****" + last_character_before_at + rest_after_last_character
```

For phone:

| Digit count | Country code length | Output format |
|---|---:|---|
| `10` | `0` | `"***-***-1234"` |
| `11` | `1` | `"+*-***-***-1234"` |
| `12` | `2` | `"+**-***-***-1234"` |
| `13` | `3` | `"+***-***-***-1234"` |

## Algorithm

Check whether the string contains `"@"`.

If yes:

1. Convert `s` to lowercase.
2. Find the index of `"@"`.
3. Return:
   ```python id="eiw7ur"
   s[0] + "*****" + s[at_index - 1:]
   ```

If no:

1. Extract all digits from `s`.
2. Let `digits` be the resulting digit string.
3. Let `local` be the last 4 digits.
4. If there are exactly 10 digits, return:
   ```python id="ou0gk0"
   "***-***-" + local
   ```
5. Otherwise, the country code length is:
   ```python id="pfory9"
   len(digits) - 10
   ```
6. Return:
   ```python id="pwn3py"
   "+" + "*" * country_length + "-***-***-" + local
   ```

## Walkthrough

Use:

```python
s = "86-(10)12345678"
```

There is no `"@"`, so this is a phone number.

Extract digits:

```python
digits = "861012345678"
```

The number of digits is:

```python
12
```

So the country code length is:

```python
12 - 10 = 2
```

The last 4 digits are:

```python
"5678"
```

Build the result:

```python
"+" + "**" + "-***-***-" + "5678"
```

which gives:

```python
"+**-***-***-5678"
```

## Correctness

There are two valid input types: email and phone number.

If the string contains `"@"`, it is an email. The algorithm lowercases the entire string, keeps the first character of the name, replaces the middle of the name with exactly five stars, and appends from the last character of the name through the domain. This matches the required email masking rule.

If the string does not contain `"@"`, it is a phone number. The algorithm removes all separator characters by keeping only digits. The last 10 digits are the local number, and all earlier digits form the country code.

The algorithm always exposes only the last 4 digits. It formats the local number as:

```python
***-***-XXXX
```

where `XXXX` is the last 4 digits.

If a country code exists, the algorithm prefixes the local format with `"+"`, one star for each country-code digit, and `"-"`. This matches the required phone masking rule.

Therefore, in both possible input cases, the algorithm returns exactly the required masked string.

## Complexity

| Metric | Value | Why |
|---|---:|---|
| Time | `O(n)` | We scan the string to lowercase or extract digits |
| Space | `O(n)` | We build the output string and, for phones, the digit string |

Here `n` is the length of `s`.

## Implementation

```python
class Solution:
    def maskPII(self, s: str) -> str:
        if "@" in s:
            s = s.lower()
            at_index = s.index("@")
            return s[0] + "*****" + s[at_index - 1:]

        digits = []

        for ch in s:
            if ch.isdigit():
                digits.append(ch)

        digits = "".join(digits)
        last_four = digits[-4:]

        if len(digits) == 10:
            return "***-***-" + last_four

        country_length = len(digits) - 10
        return "+" + "*" * country_length + "-***-***-" + last_four
```

## Code Explanation

For email detection:

```python
if "@" in s:
```

The problem guarantees valid input, so this cleanly separates email from phone number.

For email masking:

```python
s = s.lower()
at_index = s.index("@")
return s[0] + "*****" + s[at_index - 1:]
```

`at_index - 1` is the last character of the name before `"@"`.

So:

```python
s[at_index - 1:]
```

includes the last name character, the `"@"`, and the full lowercase domain.

For phone numbers, we extract digits:

```python
digits = []

for ch in s:
    if ch.isdigit():
        digits.append(ch)
```

Then:

```python
digits = "".join(digits)
last_four = digits[-4:]
```

keeps the visible part.

If there is no country code:

```python
if len(digits) == 10:
    return "***-***-" + last_four
```

Otherwise:

```python
country_length = len(digits) - 10
return "+" + "*" * country_length + "-***-***-" + last_four
```

adds the country-code prefix.

## Testing

```python
def run_tests():
    s = Solution()

    assert s.maskPII("LeetCode@LeetCode.com") == "l*****e@leetcode.com"
    assert s.maskPII("AB@qq.com") == "a*****b@qq.com"
    assert s.maskPII("1(234)567-890") == "***-***-7890"
    assert s.maskPII("86-(10)12345678") == "+**-***-***-5678"
    assert s.maskPII("+1(234)567-890") == "+*-***-***-7890"
    assert s.maskPII("+(501321)-50-23431") == "+***-***-***-3431"

    print("all tests passed")

run_tests()
```

Test meaning:

| Test | Why |
|---|---|
| Mixed-case email | Confirms lowercasing |
| Short email name | Confirms exactly five stars |
| 10-digit phone | Confirms no country code |
| 12-digit phone | Confirms two-digit country code |
| 11-digit phone | Confirms one-digit country code |
| 13-digit phone | Confirms three-digit country code |

