4
$\begingroup$

I wanted to make a simple email address validator using StringMatchQ. This was my first attempt:

simplifiedEmailPattern = StringExpression[
    Repeated[
        WordCharacter | "-" | "_" | "." | "+",
        {1, 255}
    ],
    Verbatim["@"],
    StringExpression[
        LetterCharacter,
        Repeated[WordCharacter],
        Repeated[
            StringExpression[
                "." | "-",
                    LetterCharacter,
                    Repeated[WordCharacter]
            ]
        ]
    ]
]

This works as expected for a few simple test cases:

StringMatchQ["me@gmail.com", simplifiedEmailPattern] (* True *)
StringMatchQ["user@baddomain", simplifiedEmailPattern] (* False*)
StringMatchQ["no_at_symbol.com", simplifiedEmailPattern] (* False *)

Then I discovered that Interpreter has a built-in validator which is probably more robust than anything I can make by hand, so I tried doing this:

interpreterEmailPattern = (string___ /; !FailureQ[Interpreter["EmailAddress"][string]])

which gives the same results for the above tests:

StringMatchQ["me@gmail.com", interpreterEmailPattern] (* True *)
StringMatchQ["user@baddomain", interpreterEmailPattern] (* False *)
StringMatchQ["no_at_symbol.com", interpreterEmailPattern] (* False *)

The problem is when I run profiling, the Interpreter method is MUCH slower (more than 200 times slower in fact):

In[91]:= First@
 Timing[Table[StringMatchQ["me@gmail.com", simplifiedEmailPattern], 
   10000]]

Out[91]= 0.03125

In[92]:= First@
 Timing[Table[StringMatchQ["me@gmail.com", interpreterEmailPattern], 
   10000]]

Out[92]= 7.3125

I suspect the time difference is because Interpreter is doing more than just a validation, it is also doing some kind of interpretation. But for my email address validator, I don't care about the interpretation.

My question is: Is there a way to user whatever pattern Interpreter is using to validate email addresses directly?

--

NOTE: The doc for "EmailAddress" Interpreter type states: "Use of 'EmailAddress' does not require connectivity to the Wolfram Cloud." So that is not the cause of the delay.

$\endgroup$
5
$\begingroup$

I took a few minutes to dig through the DownValues and here is a method based on what I found:

getEmails[emails_] :=
 Module[{io, prep, match, prepped, badPos, failures},
  io = Interpreter`InterpreterObject["EmailAddress"];
  prep = io["StringProcessFast"];
  match = io@"PatternRestriction";
  prepped = prep@Flatten@{emails};
  badPos = Pick[Range[Length@prepped], StringMatchQ[prepped, match], False];
  failures =
   Map[
    Failure["NotAnEmail", 
      <|
       "MessageTemplate" -> "`` is not a valid email address",
       "MessageParameters" -> {#}
       |>
      ] &,
    prepped[[badPos]]
    ];
  ReplacePart[prepped, 
   Thread[badPos -> failures]
   ]
  ]

Then this works like:

getEmails[{"asdasd", "asdasd <asdasd@asdasd.com>", "asdasd@asdasd.com", 
  "asdasd@asdasd"}]

{Failure["NotAnEmail", 
Association[
  "MessageTemplate" -> "`` is not a valid email address", 
   "MessageParameters" -> {
    "asdasd"}]], "asdasd@asdasd.com", "asdasd@asdasd.com", Failure[
 "NotAnEmail", 
Association[
  "MessageTemplate" -> "`` is not a valid email address", 
   "MessageParameters" -> {"asdasd@asdasd"}]]}

which should be faster than iterating Interpreter.

This is confirmed by:

First@Timing[Table[getEmails["me@gmail.com"], 10000]]

0.836215

Now if you are sure you don't need to pre-process your emails at all (i.e. they've already been cleaned) you can just use the Interpreter pattern like:

pat = Interpreter`InterpreterObject["EmailAddress"]["PatternRestriction"];
First@Timing[Table[StringMatchQ["me@gmail.com", pat], 10000]]

0.034786
$\endgroup$
  • $\begingroup$ For context, I am using this as a validator for an input field. I just need to verify that the email address is valid before the user can submit. Can you explain why you think the input needs to be "cleaned"? Isn't that the point of using the pattern check? $\endgroup$ – ClydeTheGhost Jun 24 at 23:11
  • 1
    $\begingroup$ @ClydeTheGhost the question is if you want to accept something like aasdasd <asdasdasd@asdasda.sdasd> or not. Interpreter does which is what the cleaning is for. You may not want to. $\endgroup$ – b3m2a1 Jun 24 at 23:28
  • $\begingroup$ I tried DownValues[Interpreter] and got an empty list. When you wrote that you took a few minutes to dig through DownValues can you clarify what you did? $\endgroup$ – Jack LaVigne Jun 25 at 12:50
  • $\begingroup$ @JackLaVigne ah sorry that should have been SubValues. Interpreter[...][x] is registered as a SubValue not a DownValue. $\endgroup$ – b3m2a1 Jun 25 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.