5
$\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["[email protected]", 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["[email protected]", 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["[email protected]", simplifiedEmailPattern], 
   10000]]

Out[91]= 0.03125

In[92]:= First@
 Timing[Table[StringMatchQ["[email protected]", 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$

1 Answer 1

6
$\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 <[email protected]>", "[email protected]", 
  "asdasd@asdasd"}]

{Failure["NotAnEmail", 
Association[
  "MessageTemplate" -> "`` is not a valid email address", 
   "MessageParameters" -> {
    "asdasd"}]], "[email protected]", "[email protected]", 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["[email protected]"], 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["[email protected]", pat], 10000]]

0.034786
$\endgroup$
4
  • $\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$ Commented Jun 24, 2019 at 23:11
  • 1
    $\begingroup$ @ClydeTheGhost the question is if you want to accept something like aasdasd <[email protected]> or not. Interpreter does which is what the cleaning is for. You may not want to. $\endgroup$
    – b3m2a1
    Commented Jun 24, 2019 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$ Commented Jun 25, 2019 at 12:50
  • 1
    $\begingroup$ @JackLaVigne ah sorry that should have been SubValues. Interpreter[...][x] is registered as a SubValue not a DownValue. $\endgroup$
    – b3m2a1
    Commented Jun 25, 2019 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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