# Specifying string patterns in DeleteCases

It seems that DeleteCases is not compatible with string patterns, at least directly (see, for example, this question). Is this true? If so, why is this the case? It seems that the earlier question did not really address this.

For example, suppose that I have a list of strings and I want to delete all strings which contain the letter "X":

DeleteCases[{"ab", "aXb"}, ___ ~~ "X" ~~ ___]


gives undesired output:

{"ab", "aXb"}


One workaround is

Select[{"ab", "aXb"}, ! StringMatchQ[#, ___ ~~ "X" ~~ ___] &]


which gives the correct output:

{"ab"}


But why doesn't DeleteCases[{"ab", "aXb"}, ___ ~~ "X" ~~ ___] work?

-
The straightforward workaround is to first filter for strings. That forces you to choose whether you want to keep or delete the non-strings. DeleteCases[{"ab", "aXb"}, s_String/;StringMatchQ[s, ___~~"X"~~___]] –  Rojo Jul 31 '12 at 22:01
You can also do: DeleteCases[{"ab", "aXb"}, _?(! StringFreeQ[#, "X"] &)] –  rm -rf Jul 31 '12 at 22:14

My guess is that WRI made an explicit design decision to separate general patterns and string patterns in order to manage ambiguities where the two syntaxes overlap.

As a simple example, consider the pattern (_~~"x")... When we draw no distinction between general patterns and string patterns, then this expression is ambiguous. If interpreted as a general pattern then it matches a sequence of two-character strings, each of which ends with "x". If interpreted as a string pattern then it matches a single string where every second character is an "x". Which did the user intend? If Mathematica arbitrarily chooses one, how can the user express the other?

I think that WRI wisely decided to side-step issues like this by keeping the two syntaxes separate. We are given the tools to change that decision if it is convenient for us. For example, we could define a wrapper function that promotes a string pattern to a general pattern:

s[pat_] := _String?(StringMatchQ[#, pat]&)

Cases[{"ab", "ax", "xa"}, s[_~~"x"]]

(* {"ax"} *)


This function gives us a means to disambiguate the example from above:

MatchQ[{"ax", "bx", "cx"}, {s[(_~~"x")..]}]

(* False *)

MatchQ[{"axbxcx"}, {s[(_~~"x")..]}]

(* True *)

MatchQ[{"ax", "bx", "cx"}, {s[(_~~"x")]..}]

(* True *)

MatchQ[{"axbxcx"}, {s[(_~~"x")]..}]

(* False *)


Alternatively, we could define a lifting operator that can convert a general pattern-matching function into one that interprets any string pattern that manifestly references StringExpression in the requested fashion:

lift[f_][a___] := f @@ Replace[{a}, p_StringExpression :> s@p, {1}]

myCases = lift[Cases];

myCases[{"ab", "ax", "xa"}, _~~"x"]

(* {"ax"} *)

lift[DeleteCases][{"ab", "ax", "xa"}, _~~"x"]

(* {"ab", "xa"} *)


Note that the definition of lift exploits Mathematica's interpretation of _~~"x" as a general pattern -- it wouldn't work if it were interpreted as a string pattern instead.

s and lift are intended as simple illustrations rather than robust functionality. lift, for example, does not handle replacement rules and it runs into the very ambiguity under discussion. Both are also likely to have other subtle and unintended behaviours that may arise in general use. But they might be useful in controlled contexts (e.g. in an application where the patterns are known to be simple and occur in large enough numbers to justify the notational convenience).

-
+1 you have my symbolic accept –  Rojo Aug 1 '12 at 13:44
What a great answer!!!! –  Thomas Aug 21 '12 at 12:00

StringMatchQ threads its first arguments. Good use of this makes for way faster alternatives to the ones suggested

dic = DictionaryLookup[Repeated[_, 5]];
spatt = "a" ~~ _ ~~ "o" ~~ _ ~~ "t";
Pick[dic, StringMatchQ[dic, spatt]] // AbsoluteTiming
Cases[dic, s_ /; StringMatchQ[s, spatt]] // AbsoluteTiming
Select[dic, StringMatchQ[#, spatt] &] // AbsoluteTiming


-
In the absence of a more objective reasoning, I can offer my conjecture. Mathematica is not a language that has robust support for string objects — at least, not to the extent that Perl and python do. Earlier versions of Mathematica had far less support for strings and you'd probably use Cases (since v1) and DeleteCases (since v2) along with StringMatchQ (since v1) and StringReplace (since v2) to get your work done.
Support for String objects greatly increased in v5.1 with the introduction of StringCases, StringExpression, StringFreeQ, etc. This was probably big enough of an undertaking that they decided not to add the functionality to existing functions (DeleteCases was last modified in v4) and run the risk of introducing bugs, but instead created new functions specifically for String objects.
This is not to say that StringCases is (or was) bug-free, but it's easier to find and fix bugs in new code than find out what new feature broke something in old code. –  rm -rf Jul 31 '12 at 22:29
I would say that the reason is dead simple and not directly related to the added functionality being just big. Cases and DeleteCases work on parsed expressions, while string functions work on strings. These are just so different that mixing them togeher would be a very wrong design decision IMO. –  Leonid Shifrin Aug 1 '12 at 11:37