16
$\begingroup$

Suppose that I have the following sample list, called list:

list = {"bBb", "aAa", "aaa"};

Now suppose I want to select the elements that do not contain the string "A". One way to do this is to use Select with StringMatchQ:

Select[list, ! StringMatchQ[#, ___ ~~ "A" ~~ ___] &]

{"bBb", "aaa"}

(See, for example, this old question.)

But, instead of selecting the elements which do not contain "A", is it possible to use Select with StringMatchQ, but this time selecting all patterns except those that match the pattern ___ ~~ "A" ~~ ___. My thought is to use Except:

Select[list, StringMatchQ[#, Except[___ ~~ "A" ~~ ___]] &]

{}

StringExpression::invld: Element Except[___ ~~ "A" ~~ ___] is not a valid string or pattern element in StringExpression[Except[___ ~~ "A" ~~ ___]]

But I get the error message above. So I looked in the documentation for StringMatchQ. It has the following three general syntax forms:

StringMatchQ["string", patt]
StringMatchQ["string", RegularExpression["regex"]]
StringMatchQ[{s1, s2, ...}, p]

Thinking about the first form, StringMatchQ takes a pattern as its second argument. I know that ___ ~~ "A" ~~ ___ is a pattern, but is Except[___ ~~ "A" ~~ ___] a pattern? According to the documentation for Except, it is. The next question is, is Except[___ ~~ "A" ~~ ___] a string pattern? According to the error message above, it would seem not. (Also, apparently StringMatchQ takes only string patterns as its second argument -- which, of course, is logical.)

So, my overall question is, why does Select[list, StringMatchQ[#, Except[___ ~~ "A" ~~ ___]] &] give an error?

$\endgroup$
3
  • 3
    $\begingroup$ I've found that Except is generally buggy with strings, which is why I always avoid it (with strings). For instance, compare: StringMatchQ["a", Except["b"]] and StringMatchQ["a", Except["bb"]] $\endgroup$
    – rm -rf
    Dec 28, 2013 at 18:22
  • 3
    $\begingroup$ @rm-rf The StringExpression doc says: Except[p] - any character except ones matching p IMHO implying that p can only be a character in this context and not a string. Behavior is therefore documented and not a bug. $\endgroup$ Dec 28, 2013 at 18:39
  • $\begingroup$ @SjoerdC.deVries Ah, good point. I forgot that StringMatchQ internally calls StringExpression on the second argument. That also explains OP's error then. You should post that as an answer. $\endgroup$
    – rm -rf
    Dec 28, 2013 at 18:45

3 Answers 3

19
$\begingroup$

When applied to string patterns Except only seems to work for string patterns that represent classes of characters or for string patterns that represent positions in strings, e.g.:

Except[{"A" | "B"}]
Except[WordCharacter]
Except[StartOfLine]

If you apply Except to an arbitrary String pattern (i.e., the head is StringExpression), the resulting expression does not seem to be a workable string expression. The documentation does not make this explicitly clear.

An alternate solution is to use StringFreeQ instead of StringMatchQ with Except:

Select[list, StringFreeQ[#, ___ ~~ "A" ~~ ___] &]

Also note that if the list is really large, using Pick in combination with the listable form of StringFreeQ is an order of a magnitude faster:

Pick[list, StringFreeQ[list, ___ ~~ "A" ~~ ___]]
$\endgroup$
11
$\begingroup$

The StringExpression doc says: Except[p]: any character except ones matching p. This seems to imply that Except[p] can only stand for a single character in this context and not a string.

$\endgroup$
3
  • 5
    $\begingroup$ WRI is the master of implicit documentation... $\endgroup$
    – Kuba
    May 21, 2015 at 8:05
  • $\begingroup$ @Christ, this hasn't been fixed since 2013? $\endgroup$ May 13, 2019 at 22:16
  • $\begingroup$ It's 2022, I'm trying to use Except in the same way and it's still not fixed lol $\endgroup$
    – atlasgeo
    Apr 10, 2022 at 19:11
3
$\begingroup$

As a supplement to sakra's answer, here's one way of using Except with whole strings as arguments. Suppose you have two lists:

countries = {"PitcairnIslands", "Jordan", "Serbia", "Greece", "Zimbabwe"};
continents = {"Oceania", "Asia", "Europe", "Europe", "Africa"};

We want to Pick those countries, which are not in Europe. A first – obvious – attempt fails:

Pick[countries, continents, Except["Europe"]]

yields

{PitcairnIslands, Jordan, Serbia, Greece, Zimbabwe}

The trick is, you have to add a "parent class of pattern" as a second argument in Except. This works:

In[121]:= Pick[countries, continents, Except["Europe", _String]]

gives the desired output.

{"PitcairnIslands", "Jordan", "Zimbabwe"}

I admit this is somewhat counterintuitive for such Except constructs. Hope that helps a little!

$\endgroup$

Your Answer

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

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