<p>Every <strong>valid email</strong> consists of a <strong>local name</strong> and a <strong>domain name</strong>, separated by the <code>&#39;@&#39;</code> sign. Besides lowercase letters, the email may contain one or more <code>&#39;.&#39;</code> or <code>&#39;+&#39;</code>.</p>

<ul>
	<li>For example, in <code>&quot;alice@leetcode.com&quot;</code>, <code>&quot;alice&quot;</code> is the <strong>local name</strong>, and <code>&quot;leetcode.com&quot;</code> is the <strong>domain name</strong>.</li>
</ul>

<p>If you add periods <code>&#39;.&#39;</code> between some characters in the <strong>local name</strong> part of an email address, mail sent there will be forwarded to the same address without dots in the local name. Note that this rule <strong>does not apply</strong> to <strong>domain names</strong>.</p>

<ul>
	<li>For example, <code>&quot;alice.z@leetcode.com&quot;</code> and <code>&quot;alicez@leetcode.com&quot;</code> forward to the same email address.</li>
</ul>

<p>If you add a plus <code>&#39;+&#39;</code> in the <strong>local name</strong>, everything after the first plus sign <strong>will be ignored</strong>. This allows certain emails to be filtered. Note that this rule <strong>does not apply</strong> to <strong>domain names</strong>.</p>

<ul>
	<li>For example, <code>&quot;m.y+name@email.com&quot;</code> will be forwarded to <code>&quot;my@email.com&quot;</code>.</li>
</ul>

<p>It is possible to use both of these rules at the same time.</p>

<p>Given an array of strings <code>emails</code> where we send one email to each <code>emails[i]</code>, return <em>the number of different addresses that actually receive mails</em>.</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> emails = [&quot;test.email+alex@leetcode.com&quot;,&quot;test.e.mail+bob.cathy@leetcode.com&quot;,&quot;testemail+david@lee.tcode.com&quot;]
<strong>Output:</strong> 2
<strong>Explanation:</strong> &quot;testemail@leetcode.com&quot; and &quot;testemail@lee.tcode.com&quot; actually receive mails.
</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> emails = [&quot;a@leetcode.com&quot;,&quot;b@leetcode.com&quot;,&quot;c@leetcode.com&quot;]
<strong>Output:</strong> 3
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>1 &lt;= emails.length &lt;= 100</code></li>
	<li><code>1 &lt;= emails[i].length &lt;= 100</code></li>
	<li><code>emails[i]</code> consist of lowercase English letters, <code>&#39;+&#39;</code>, <code>&#39;.&#39;</code> and <code>&#39;@&#39;</code>.</li>
	<li>Each <code>emails[i]</code> contains exactly one <code>&#39;@&#39;</code> character.</li>
	<li>All local and domain names are non-empty.</li>
	<li>Local names do not start with a <code>&#39;+&#39;</code> character.</li>
	<li>Domain names end with the <code>&quot;.com&quot;</code> suffix.</li>
</ul>


size_t hashEmail(string_view email) {
    size_t hash = 5381;

    const string_view::size_type atIndex = email.find('@');
    const string_view localName = email.substr(0, atIndex);
    const string_view domainName = email.substr(atIndex + 1);


    for (string_view::size_type i = 0; i < localName.size() && localName[i] != '+'; i++) {
        if (localName[i] == '.') continue;
        hash = ((hash << 5) + hash) + static_cast<unsigned char>(localName[i]); /* hash * 33 + c */
    }
    
    for (char c : domainName) {
        hash = ((hash << 5) + hash) + static_cast<unsigned char>(c);
    }

    return hash;
}

bool sameEmail(string_view email1, string_view email2) {
    const string_view::size_type atIndex1 = email1.find('@');
    const string_view::size_type plusIndex1 = email1.find('+');
    const string_view localName1 = email1.substr(0, std::min(atIndex1, plusIndex1));
    const string_view domainName1 = email1.substr(atIndex1 + 1);

    const string_view::size_type atIndex2 = email2.find('@');
    const string_view::size_type plusIndex2 = email2.find('+');
    const string_view localName2 = email2.substr(0, std::min(atIndex2, plusIndex2));
    const string_view domainName2 = email2.substr(atIndex2 + 1);

    string_view::size_type i1 = 0;
    string_view::size_type i2 = 0;
    while (i1 < localName1.length() && i2 < localName2.length()) {
        while (i1 < localName1.length() && localName1[i1] == '.')
            i1++;
        while (i2 < localName2.length() && localName2[i2] == '.')
            i2++;
        if (i1 < localName1.length() && i2 < localName2.length() && localName1[i1] != localName2[i2])
            return false;
        i1++;
        i2++;
    }

    while (i1 < localName1.length() && localName1[i1] == '.')
        i1++;
    while (i2 < localName2.length() && localName2[i2] == '.')
        i2++;

    return i1 >= localName1.length() && i2 >= localName2.length() && domainName1 == domainName2;
}


class Solution {
public:

    int numUniqueEmails(vector<string>& emails) {
        int res = static_cast<int>(emails.size());
        std::unordered_map<std::size_t, string_view> found;

        for (const string& email : emails) {
            const size_t hash = hashEmail(email);

            try {
                res -= sameEmail(found.at(hash), email);
            } catch (const std::out_of_range& err) {
                found.insert({ hash, email });
            }
        }

        return res;
    }
};
17400