Let the following exprs stand for a list of complicated expressions which we are interested in breaking down into parts by extracting a number of individual subcomponents, from each expression:

exprs = {f1["b", f2[1], head1["a", 14] f3["c", head2[12, 3], head1["b", 8]]] head2[2, 8], f2[5]};

The patterns we are interested in are of the following forms:

patts = {head1["a", x1_], head2[x2_,y_]};

Now, here comes what is troubling me:

  • The obvious way ( to me ) to extract the desired parts ( here x1, x2 and y) is to use Cases:

    Cases[exprs, #->{x1, x2, y}, Infinity]& /@ patts

which in turn returns a list of the requested parts per expression

{{{14,x2,y}} ,{{x1,12,3}, {x1,2,8}}}

This way of getting the requested parts out is not productive in the sense that when having to extract 10 or 20 parts or more, providing Cases with an exhaustive list is a no-go. I need something more automated.

One possible solution I could come up with was to use something like syms = Cases[patts, p_Pattern :> p[[1]], Infinity] to extract beforehand all the symbols associated with the desired subexpressions, since the patterns are handcoded and are known before the extraction 'starts'. The amended Cases and its return value are shown below

Cases[exprs, #->syms, Infinity]& /@ patts
{{{14,x2,y}}, {{x1,12,3}, {x1,2,8}}}

This is still not 100% usable because it would be nice if there were a way to postprocess the extracted subexpressions after they are identified.

One possible way to do that is

Cases[exprs, # -> postproc[syms], Infinity]& /@ patts

which evaluates to

{{postproc[{14,x2,y}]}, {postproc[{x1,12,3}], postproc[{x1,2,8}]}}

which seems to do the trick, but brakes down as soon as I try to define postproc in a way I find useful, eg. as in the following sense.

keys=ToString /@ syms;
postproc[x_, h_: keys] := AssociationThread[h -> x]
     {<|"x1"->x1,"x2"->x2,"y"->y|>, |<|"x1"->x1,"x2"->x2,"y"->y|>}}

As anyone can see, as soon as postproc gets a definition the output is not evaluated in the desired way.

I've tried several iterations for the definition of postproc along with interchanging Rule with RuleDelayed in the second argument of Cases. The only thing I could come up that works is Holding the head of postproc.

Cases[exprs, #->Hold[postproc][syms],Infinity] & /@ patts // ReleaseHold
     {<|"x1"->x1,"x2"->12,"y"->3|>, <|"x1"->x1,"x2"->2,"y"->8|>}}

OK, so this works but I don't know if there's a better/more intuitive/more paradigmatic way to code something like this.

Please help, any suggestion is appreciated.


One part of the problem has already been mentioned by @C.E. in his answer: Since Association is seen as atomic by the pattern matcher, no insertion of matched expressions happens within it.

The reason this is a problem at all is that your postproc is not actually evaluated after the matches have been identified, but before. To prevent evaluation of the right side of the replacement rule before the match has been found, you can use RuleDelayed (:>)

 {syms = syms},
 Cases[exprs, # :> postproc[syms], Infinity] & /@ patts
(* {
    {<|"x1" -> 14, "x2" -> x2, "y" -> y|>},
    {<|"x1" -> x1, "x2" -> 12, "y" -> 3|>, <|"x1" -> x1, "x2" -> 2, "y" -> 8|>}
   } *)

Note the use of With to insert the value of syms into the now held right side of the rule (if syms were to be evaluated after the match is found, nothing would be inserted, since x1, x2, etc. need to be literally present in the replacement).

  • $\begingroup$ so please let me see if I get it right: using RuleDelayed without injecting anything (not using With) does not allow syms to evaluate to their OwnValues because RuleDelayed Holds its second argument (its Attributes contain HoldRest); this way, when something matches, postproc is called with syms which eventually gets evaluated to its unbound OwnValues; on the other hand, using With is-essentially-like manually supplying postproc with the OwnValues of syms; this way, when a match is found, the RHS of the rule is evaluated with the bound values $\endgroup$ Nov 23 '19 at 14:09
  • 1
    $\begingroup$ @yosimitsukodanuri That exactly what is happening, yes $\endgroup$
    – Lukas Lang
    Nov 23 '19 at 14:14

A possible workaround:

keys = ToString /@ syms;
postproc[x_, h_: keys] := Thread[h -> x]

Association @@@ Cases[exprs, # -> postproc[syms], Infinity] & /@ patts

{{<|"x1" -> 14, "x2" -> x2, "y" -> y|>}, {<|"x1" -> x1, "x2" -> 12,
"y" -> 3|>, <|"x1" -> x1, "x2" -> 2, "y" -> 8|>}}

  • $\begingroup$ Please explain how that works because I'm not sure I understand how it applies to the problem $\endgroup$ Nov 23 '19 at 12:43
  • $\begingroup$ @yosimitsukodanuri well, it applies to the problem in the sense that it gets the same answer that you were expecting from your method. The problem is that symbol replacement in Association doesn't work the way you'd expect because Association is atomic (in theory, WRI may not be consistent about this). So instead I use a list first, which is not atomic and supports replacements, and then convert the list structure to associations at the last moment. $\endgroup$
    – C. E.
    Nov 23 '19 at 12:51
  • $\begingroup$ oh, ok now I saw what you did with Thread; thank you for the workaround and for pointing out that the 'problem' I am dealing with is due to the atomic nature of Association $\endgroup$ Nov 23 '19 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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