14
$\begingroup$

Mathematica provides Alternatives, to match one of several patterns. In a rule, this is used as p1|p2|p3:>replacement. I would like to have the opposite logic: p1&p2&p3:>replacement (obviously using another notation) should match if all of p1, p2, and p3 match, and replacement should be allowed to involve variables in all of p1, p2, p3.

If one is simply concerned in whether all patterns match, one can use Except[Except[p1] | Except[p2] | Except[p3]], as described in Opposite of Alternatives or Logical AND of multiple patterns. I would like to do the same with rules, but the doubel-Except construction will not populate named patterns (at least in v10.0 I get an Except::named message).

Cases[{2/3, 1 + Pi, 3 - Gamma[I], 1 + x}, 
  AndRuleDelayed[Plus[a_, b_], c_?NumericQ, {a, b, c}]]

(* should give
  {{1, Pi, 1 + Pi}, {3, -Gamma[I], 3 - Gamma[I]}}
*)
$\endgroup$

2 Answers 2

11
$\begingroup$

The following code defines AndRuleDelayed to take a number of patterns followed by a (single) replacement and build a rule. The rule matches if and only if all patterns match, and the replacement can involve variables from any of the patterns. If the same variable name appears in two patterns, the combined pattern will only match if all occurrences of that name match the same thing (exactly like Mathematica does normally in patterns like {a_,a_}).

Clear[AndRuleDelayed];
AndRuleDelayed::usage = 
  "AndRuleDelayed[p1_,p2_,...,rhs_] is p1&p2&...:>rhs.";
Begin["AndRule`"];
SetAttributes[AndRuleDelayed, HoldAll];
SyntaxInformation[AndRuleDelayed] = {"ArgumentsPattern" -> {_, __}};
AndRuleDelayed[patts__, rhs_] :=
  (total_ :>
    With[{result = 
       Cases[{Table[total, {Length[{patts}]}]}, {patts} :> rhs]},
     result[[1]] /; result =!= {}]);
End[];

(I've put the code in a scope because I suspect that there may be issues with name collisions otherwise.) Examples:

Cases[{2/3, 1 + Pi, 3 - Gamma[I], 1 + x}, 
  AndRuleDelayed[Plus[a_, b_], c_?NumericQ, {a, b, c}]]

(*=> {{1, Pi, 1 + Pi}, {3, -Gamma[I], 3 - Gamma[I]}}*)

Cases[{{5, 2, 3, 5}, {3, 4}, {6, 3, 6}}, 
  AndRuleDelayed[{___, 3, a_}, {a_, b_, ___}, l_List, 
   g[a, b, Length[l]]]]

(*=> {g[5, 2, 4], g[6, 3, 3]}*)
$\endgroup$
7
  • 1
    $\begingroup$ This is nice. +1. You could perhaps use FirstCase to make it slightly more compact: AndRuleDelayed[patts__, rhs_] := (total_ :> With[{result = FirstCase[{Table[total, {Length[{patts}]}]}, {patts} :> rhs]}, result /; ! MissingQ@result]);. $\endgroup$ Commented May 30, 2016 at 21:29
  • $\begingroup$ @LeonidShifrin, thanks, I didn't know FirstCase. The only drawback is if for some reason the user wants rhs to expand to a Missing[...] construction, which would be considered as a failed match. $\endgroup$ Commented May 31, 2016 at 13:47
  • $\begingroup$ Indeed, you are right again. I didn't think about it, good point! Your solution is the way to go, then. $\endgroup$ Commented May 31, 2016 at 14:04
  • 1
    $\begingroup$ @Algohi: In some circumstances which I don't know in detail, Mathematica renames variables by adding a dollar at the end. Here it seems to be due to an interaction between an assignment and a rule. The same happens if you do f[x_]:=(t:>x+t). Then f[a] evaluates to t$_ :> a + t$. This renaming is useful so that f[t] evaluates as expected to a rule that adds t. However, f[t$] will evaluate to a rule that multiplies by 2. This leads to occasional bugs (name collisions) when several constructions lead Mathematica to add $ to the same variable names. $\endgroup$ Commented Jun 2, 2016 at 13:41
  • 1
    $\begingroup$ @BrunoLeFloch Regarding renaming and name collisions, you may find this discussion interesting. It also contains a number of links to other discussions of the similar issues, as well as discussions which linked that one. $\endgroup$ Commented Jun 4, 2016 at 11:54
0
$\begingroup$

I like this question and I know I have played with the concept before, either for a question here or for my own interest. (I cannot recall.)

I also like your method but I am immediately struck by the fact that it will not work with held expressions, for example:

Hold[1 + 2] /. AndRuleDelayed[a_ + b_, c_?NumericQ, {a, b, c}]
Hold[1 + 2] /. (a_ + b_) :> {a, b, c}
Hold[1 + 2] /. c_?NumericQ :> {a, b, c}
Hold[1 + 2]

Hold[{1, 2, c}]

Hold[{a, b, 1 + 2}]

I propose this as a partial improvement:

Attributes[multiRuleD] = {HoldAll};

multiRuleD[patts__, rhs_] := total_ :>
  With[
    {result =
      Cases[
        {Join @@ (Hold[total] & /@ {patts})},
        Hold[patts] :> rhs
      ][[1]] // Quiet},
   result /; Quiet[result =!= {}[[1]]]
  ]

Now:

Hold[1 + 2] /. multiRuleD[a_ + b_, c_?NumericQ, {a, b, c}]
Hold[{1, 2, 3}]

Ideally this should have returned Hold[{1, 2, 1 + 2}] but I cannot recall a way to prevent that evaluation in a self-contained way.

Note:

  • Use of Hold[total] & /@ {patts} is hackish but fun. It will cause problems if explicit Slot expressions exist within total. Since my implementation is still not complete I left it in for now. If I solve the evaluation issue above and will write my code more robustly.
$\endgroup$

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.