4
$\begingroup$

In this toy example,

lis = {C[1]*3, C[2]*9, 123};

I want to change, using Cases, the pattern C[x__]*y_ to C[x]*99*y and the pattten y_Integer to 5*y

Currently I do it using two separate calls and then use Join on the result. Like this

ClearAll["Global`*"]
lis = {C[1]*3, C[2]*9, 123};
c1 = Cases[lis, C[x__]*y_ :> C[x]*99*y]
c2 = Cases[lis, y_Integer :> 5*y]
Join[c1, c2]

Mathematica graphics

Is it possible to do it using just one call to Cases?

Cases[lis, ??? ]

Not able to find what the syntax could be (if it is even possible).

Alternative does not do what I want (at least I could not make it work).

Ofcourse this should extend to more than just two, it should work with 3 different patterns and 4 and as many as one wants.

Update

The above toy example may be was too toy. Here is another example

ClearAll["Global`*"]
lis = {Sin[x], Cos[x], 99, x};
c1 = Cases[lis, Sin[x_] :> 3 + Sin[2*x]]
c2 = Cases[lis, Cos[x_] :> Exp[x]*Tan[x]]
c3 = Cases[lis, x_Integer :> Integrate[x, z]]
Join[c1, c2, c3]

Mathematica graphics

The idea to keep the same patterns used for each case, but combine them into one call to Cases. I do not want to change the logic or anything like that. In a new universe, I'd like to do for the above this

Cases[lis, Sin[x_] :> 3 + Sin[2*x] | 
           Cos[x_] :> Exp[x]*Tan[x]] |
           x_Integer :> Integrate[x, z]
     ]

But the above ofcourse does not work.

$\endgroup$
8
  • $\begingroup$ Cases[lis, a_. * y_Integer :> a*99*y, 1] $\endgroup$
    – Bob Hanlon
    Commented Mar 2, 2023 at 0:52
  • $\begingroup$ @BobHanlon I think I put an example which makes yours work. the 99 is not meant to be the same. It can be anything for each pattern. I will correct my example to make it general. The patterns have nothing in common in practice. $\endgroup$
    – Nasser
    Commented Mar 2, 2023 at 0:54
  • 1
    $\begingroup$ I would probably use Join[Cases[lis, #][[1]] & /@ {Sin[x_] :> 3 + Sin[2*x], Cos[x_] :> Exp[x]*Tan[x], x_Integer :> Integrate[x, z]}] $\endgroup$
    – Bob Hanlon
    Commented Mar 2, 2023 at 1:58
  • 2
    $\begingroup$ cases = {c1, c2, c3}; and NestList[Cases[lis, #] &, cases, Length@cases] // Flatten ? $\endgroup$
    – Syed
    Commented Mar 2, 2023 at 3:29
  • 4
    $\begingroup$ You can use Replace with your list of patterns + a falling pattern to remove them at the end like _ -> Nothing, Example 1: Replace[lis, {C[x__]*y_ :> C[x]*99*y, y_Integer :> 5*y, _ -> Nothing}, {1}], Example 2: Replace[lis, {Sin[x_] :> 3 + Sin[2*x], Cos[x_] :> Exp[x]*Tan[x], x_Integer :> Integrate[x, z], _ -> Nothing}, {1}] $\endgroup$
    – Ben Izd
    Commented Mar 2, 2023 at 4:33

3 Answers 3

3
$\begingroup$

1. Why (I think) it doesn't work?

The problem with the proposed solution is (probably) that Cases needs its second argument to be a pattern (or a pattern Rule-or RuleDelayed which is kind of the same thing, for our purposes here- something like eg. x_:>f[x]); however, the proposed solution (where the question is driving at), uses a list of patterns, instead. We all know, that a list of patterns is not (necessarily?) a pattern itself.

Therefore, it should come as no surprise when something like this doesn't perform as expected:

(* input *)
lis = {Sin[x], Cos[x], 99, x};
rls = {Sin[x_] :> 3 + Sin[2*x], Cos[x_] :> Exp[x]*Tan[x], x_Integer :> Integrate[x, z]};

(* evaluation *)
Cases[lis, rls]

(it evaluates to {}, which is not what one expected to get.)

1.1 What would be a possible solution?

Seeing as having a second argument to Cases that is not a pattern, is (or, might be) the problem, one possible solution, would be to transform the list of pattern rules into a pattern itself; one way to achieve this is by using Alternatives:

patt = p : Alternatives @@ rls[[All, 1]]
Cases[lis, patt :> sel[p]] 

Now, sel[p] is a function we need to define, which will accept as an argument, the matched left-hand-side of a target pattern and will produce the desired right-hand-side of that pattern, eg. sel[Sin[x]] should produce 3+Sin[2x] etc.

This is a possible solution, however it will not be pursued further in this note of an answer because it suggests a path where at some point the user will have to define sel[p] for all admissible/desirable patterns in their input and that seems (it might possibly not be the case, however it feels like that at the present moment of writing) cumbersome.

2. What could possibly work?

One possible solution, which, however, needs to repeatedly apply Cases (here, using Fold) on the list of pattern rules, is the following:

rls // cases[lis]

which evaluates to

{3+Sin[2 x], E^x Tan[x], 99 z}

The code

(* case is expected to work with a single pattern rule *)
case[list_] := (Cases[list, #] &);
(* join will combine a previous result with the current output of case *)
join[list_] := (Join[#1, case[list][#2]] &);
(* fold will apply join on a list of arguments (pattern rules) *)
fold[list_] := (Fold[join[list], ##] &);
(* doFold simply applies fold on an appropriate list of pattern rules *)
doFold[list_] := Apply[fold[list]];

(* init prepares a list of pattern rules {p1,p2,...,pn} into {{p1}, {p2,...,pn}} *)
init = (TakeDrop[#, 1] &);

(* mapAt initializes the first element in the fold *)
mapAt[list_] := (MapAt[Apply[case[list]], #, 1] &);

(* cases combines all the previous steps in one function *)
cases[list_] := init/*mapAt[list]/*doFold[list]
$\endgroup$
3
$\begingroup$

Thanks for all the input and comments.

I thought to share a solution I came up with. What I really wanted is to apply a set of patterns on input, and have those expressions in the input which match the pattern be transformed, and those that do not match any pattern to remain the same.

Cases does not allow one to do it all at once. This solution below uses Scan to scan the input one item at a time, and for each item, it uses MatchQ on each rule collecting all matched hits. If no hit, then the item remains as is.

Here are few example usage. In all example the setup is the same. We define the input and the rules, and it does the rest

Example 1

input = {C[1]*3, C[2]*9, 123};
rules = {C[x__]*y_ :> C[x]*99*y, y_Integer :> 5*y};

Flatten@Last@Reap@Scan[Sow@process[#, rules] &, input]

Mathematica graphics

Example 2

input = {Sin[x], Cos[x], 99, x};
rules = {Sin[x_] :> 3 + Sin[2*x], Cos[x_] :> Exp[x]*Tan[x], x_Integer :> Integrate[x, z]};

Flatten@Last@Reap@Scan[Sow@process[#, rules] &, input]

Mathematica graphics

Example 3

input = {Sin[x], Cos[x], 9, x, Pi};
rules = {Sin[x_] :> 3 + Sin[2*x], Cos[x_] :> Exp[x]*Tan[x], x_Integer :> x^2};
Flatten@Last@Reap@Scan[Sow@process[#, rules] &, input]

Mathematica graphics

Code V 0.1 beta

process[item_, rules_] := Module[{n, matchFound = False, result},
  result = Last@Reap@Do[
      If[ MatchQ[item, First@rules[[n]]],
       Sow[item /. rules[[n]]];
       matchFound = True;
       ], {n, Length[rules]}
      ];
  If[Not[matchFound], result = item];
  result
  ]
$\endgroup$
5
  • 2
    $\begingroup$ (+1) Although it doesn't answer your question, Through[(Cases/@rules)[input]]//Flatten as a possible 'workaround'? $\endgroup$
    – user1066
    Commented Mar 2, 2023 at 12:45
  • $\begingroup$ @user1066 yes, it is a workaround, because Cases does not allow one to having multiple named patterns each with its own :>. But your workaround does not do that I really wanted, which is to also keep terms which do not match any rule, remain as is. Compare !Mathematica graphics with !Mathematica graphics. But thanks for your works. I know I did not mention that I want to keep terms that do not change in the output. This was something I found is useful later on. $\endgroup$
    – Nasser
    Commented Mar 2, 2023 at 13:54
  • 2
    $\begingroup$ Ok, and if that is the case (you want to keep terms that do not match any rules), I have certainly misinterpreted the question. But if so, why not use Replace? For example, Replace[input2, rules2, {1}] . I am probably still misinterpreting your question. $\endgroup$
    – user1066
    Commented Mar 2, 2023 at 15:02
  • $\begingroup$ input2={Sin[x],Cos[x],9, x, Pi} and rules2={Sin[x_] :> 3 + Sin[2*x],Cos[x_] :> Exp[x]*Tan[x],x_Integer :> x^2} $\endgroup$
    – user1066
    Commented Mar 2, 2023 at 15:03
  • $\begingroup$ @user1066 because I did not think of using Replace directly, I used it inside the process function when MatchQ is true. But your's is much simpler. $\endgroup$
    – Nasser
    Commented Mar 2, 2023 at 15:09
2
$\begingroup$
ClearAll[foo]

foo = Function[, 
   alt : Alternatives @@ #[[All, 1]] :> ReplaceAll[#] @ alt, 
   HoldFirst] ;

Examples:

input1 = {C[1]*3, C[2]*9, 123};
rules1 = {C[x__]*y_ :> C[x]*99*y, y_Integer :> 5*y};

Cases[input1, foo @ rules1]
{297 C[1], 891 C[2], 615}
input2 = {Sin[x], Cos[x], 99, x};
rules2 = {Sin[x_] :> 3 + Sin[2*x],
    Cos[x_] :> Exp[x]*Tan[x], 
    x_Integer :> Integrate[x, z]};

Cases[input2, foo @ rules2]
{3 + Sin[2 x], E^x Tan[x], 99 z}
input3 = {Sin[x], Cos[x], 9, x, Pi};
rules3 = {Sin[x_] :> 3 + Sin[2*x], 
    Cos[x_] :> Exp[x]*Tan[x], 
    x_Integer :> x^2};

Cases[input3, foo @ rules3]
{3 + Sin[2 x], E^x Tan[x], 81}
$\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.