14

Mathematica provides an equivalent for "logical OR" when pattern matching, using Alternatives or the infix | operator. For instance, if I want to selected all lists and strings from a list, I could do that using:

Cases[input, _List | _String]

It appears that there is no equivalent way to combine several patterns with "logical AND", i.e. to require that all of the patterns match. As a simple example, say I have a list and I want to keep those lists that contain both a, b and b, c as sublists, but I don't mind what order those sublists are in, and they could even overlap on the b. Sure, I can do:

Cases[Cases[input, {___, a, b, ___}], {___, b, c, ___}]

or list all of the possibilities with |:

Cases[input, {___, a, b, c, ___} 
             | {___, a, b, ___, b, c, ___}
             | {___, b, c, ___, a, b, ___}]

However, the latter is absolutely infeasible, since the number of combinations will quickly blow up if there are more basis patterns and some combinations might not even be easily expressible as a single pattern. The former, is okay in this case, but having to call Cases twice seems annoying, and I'm sure there are other use cases of patterns, where I need to check for both patterns in a single pass.

One way to combine two patterns is to move one of them off to a PatternTest or Condition:

Cases[input, {___, a, b, ___} ? MatchQ[{___, b, c, ___}]]

Or as J. M. suggested in chat, just defer all of them to a condition and use the main pattern only as a dummy variable:

Cases[input, x_ /; MatchQ[x, {___, a, b, ___}] && MatchQ[x, {___, b, c, ___}]]

But none of these strike me as particularly nice solutions that follow "the Mathematica way".

Long story short, is there an idiomatic way to combine multiple patterns such that an object has to match all of those patterns? If not, am I overlooking better possibilities than the ones I've listed? Ideally there should be little syntactical and computational overhead for every additional pattern in the combination.

Martin Ender
  • 8,774
  • 1
  • 34
  • 60
  • 4
    It is somewhat ugly, but you can use something like this: Cases[input, Except[Except[{___, a, b, ___}] | Except[{___, b, c, ___}]]] – Leonid Shifrin Apr 06 '16 at 12:31
  • @LeonidShifrin I think that's already an improvement over what I have above, although I probably wouldn't use it without wrapping it in a function. But using deMorgan is reasonably intuitive, and I think that should still have the short-circuiting properties you'd expect from any solution for this issue. (Still, a solution that doesn't make me cringe without wrapping it in a function would be nice, but that's probably hopeless without a built-in. I'm kinda surprised that this isn't a reasonably common problem.) – Martin Ender Apr 06 '16 at 13:35
  • How do you feel about using Fold to repeatedly apply your cases to remaining results? Fold[Cases[#1,#2]&,input,patternList] – N.J.Evans Apr 06 '16 at 15:04
  • 3
    @N.J.Evans That's very specific to the Cases application. Ideally I want a solution that I can insert anywhere Mathematica expects a single pattern. – Martin Ender Apr 06 '16 at 15:06
  • 4
    Sure, that's trivial to do: AndPattern[patts___] := Except[Alternatives @@ Map[Except, {patts}]]. Then it looks more pleasant: Cases[input, AndPattern[{___, a, b, ___}, {___, b, c, ___}]]. I don't know a better way for this problem. OTOH, frankly, I never needed it. Whether it really means that such cases are rare in practice, or is a Sapir-Whorf phenomena at work, I can't say. – Leonid Shifrin Apr 06 '16 at 15:27
  • 1
    By using OrderlessPatternSequence[PatternSequence[...], PatternSequence[..]] and by defining additional patterns which catch potential overlaps you might reduce the number of patterns you have to write. It will be like And if there are no overlaps. Just mentioning it for completeness' sake. – C. E. Apr 06 '16 at 15:54
  • 1
    @LeonidShifrin "There is no established idiom, but here is a way to write a simple wrapper without major drawbacks." would be a valid answer. ;) – Martin Ender Apr 06 '16 at 15:55
  • Not what you want, but for fun : MatchQ[{{___, a, __, c, __}, {__, c, d, c, __}}, {__?(MatchQ[{a, b, c, d, c, d, a, b}, #] &)}]. The only positive thing here is that the tests stop as soon as one is False (as you can watch using Sowand Reap) – SquareOne Apr 06 '16 at 20:40

0 Answers0