4
$\begingroup$

Let's see how MMA handles following type of expression :

Replace[L, {pattTest1->rhs1, pattTest2->rhs2, pattTest3->rhs3}, {1}]

Specific example :

In[1] Replace[Range[40], {_?(#^2<10&)->1,_?(#^2<100&)->2,_?(#^2<1000&)->3}, {1}]

Out[1] {1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,
    3,3,3,3,3,3,3,3,3,32,33,34,35,36,37,38,39,40}

Below is what goes on in the core of MMA :

1 ) take the first element of L (denoted by e1)
2 ) let # = e1
3 ) see pattTest1, and if it contains #, substitute # with e1
4 ) see if the pattTest1 is true
5 ) if true, replace it to rhs1, if not, go to pattTest2->rhs2 step
(Suppose that this time, it is not true)
6 ) see pattTest2, and if it contains #, substitute # with e1
7 ) see if the pattTest2 is True
8 ) if true, replace it to rhs2, if not, go to pattTest3->rhs3 step
(Suppose that this time, it is true)
9) Now e1 has been replaced to rhs2 and everything related to e1 is completely done.
10) # is not e1 any more. Now # is not assigned any value.
11 ) take the second element of L (denoted by e2)
12 ) let # = e2
...

But I want to modify the code In[1] so that following additional job is done in MMA :

2.1 ) introduce a temporary variable m, let m = #^2. Since # is e1 now, m = (e1)^2.
(note that (e1)^2 is evaluated, and m is set to that evaluated value)
3.1 ) see pattTest1, and if it contains m, substitute m with (e1)^2
6.1 ) see pattTest2, and if it contains m, substitute m with (e1)^2
10.1) m is not (e1)^2 any more. Now m is not assigned any value.

To make it easier to read :

1 ) take the first element of L (denoted by e1)
2 ) let # = e1
2.1 ) introduce a temporary variable m, let m = #^2. Since # is e1 now, m = (e1)^2.
3 ) see pattTest1, and if it contains #, substitute # with e1
3.1 ) see pattTest1, and if it contains m, substitute m with (e1)^2
4 ) see if the pattTest1 is true
5 ) if true, replace it to rhs1, if not, go to pattTest2->rhs2 step
(Suppose that this time, it is not true)
6 ) see pattTest2, and if it contains #, substitute # with e1
6.1 ) see pattTest2, and if it contains m, substitute m with (e1)^2
7 ) see if the pattTest2 is True
8 ) if true, replace it to rhs2, if not, go to pattTest3->rhs3 step
(Suppose that this time, it is true)
9) Now e1 has been replaced to rhs2 and everything related to e1 is completely done.
10) # is not e1 any more. Now # is not assigned any value.
10.1) m is not (e1)^2 any more. Now m is not assigned any value.
11 ) take the second element of L (denoted by e2)
12 ) let # = e2
...

I emphasize there must not exist step like
5.1 ) introduce a temporary variable m, let m = (e1)^2
just after 5 ). Because it was already done in 2.1 )

Here is a code that I've tried and failed :

In[2]:= Replace[Range[40], Module[{m},m=#^2;{_?(m<10&)->1,
_?(m<100&)->2,_?(m<1000&)->3}], {1}]

Out[2]= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40}

Finally I want to emphasize that, this time, my question is not about solving a specific problem.

$\endgroup$

1 Answer 1

5
$\begingroup$

The reason your attempt fails is that at the time the Function is applied to its arguments, m has not yet been replaced by #^2, so the # is not replaced by the argument of the function:

Module[{m = #^2}, m &[1]]
(* #1^2 *)

Having said that, it wouldn't work the way you want anyway: You need to "capture" the value of # at the point of the Module, if you want to ensure that the computation only happens once. That means that you need to insert another layer of evaluation between the application of the individual rules and the initial visiting of the element. So you need "Visit element 1" -> "Compute (Element 1)^2" -> "Try to apply rules". This is not doable with a single application of Replace.

With this in mind, here's one way (Ignoring whether this results in readable code):

Replace[Range[40], x_ :> Module[{m = x^2},
   Replace[
    x, {_?(m < 10 &) -> 1, _?(m < 100 &) -> 2, _?(m < 1000 &) -> 3}]
   ],
 {1}
 ]
(* {1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, \
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 32, 33, 34, 35, 36, 37, 38, 39, 40} *)

Or written using Map for the outer layer (which I find more readable):

Map[Module[{m = #^2},
   Replace[#, {_?(m < 10 &) -> 1, _?(m < 100 &) -> 2, _?(m < 1000 &) ->
       3}]
   ] &,
 Range[40]
 ]
(* same output *)

Here is an alternative that I would prefer in this specific use case:

Map[
 With[{m = #^2},
   Which[
    m < 10, 1,
    m < 100, 2,
    m < 1000, 3,
    True, #
    ]
   ] &,
 Range[40]
 ]
(* same output *)

Effectively, if you're using Replace-like functions to test only for non-structural properties, you should use Which or similar instead. (The reason being that all you're doing in your replacement rules is to apply a PatternTest, which leads to a lot of syntax & runtime overhead)

$\endgroup$
3
  • $\begingroup$ Thank you very much! I tested performance also about the problem mathematica.stackexchange.com/questions/268208/… $\endgroup$
    – imida k
    May 14 at 12:05
  • $\begingroup$ And the result is : Original long code(not using module) : 0.37s, Lukas Lang's 1st and 2nd code :1.5s 3rd code: 0.35s $\endgroup$
    – imida k
    May 14 at 12:09
  • $\begingroup$ So, I strongly recommend to use 3rd code(using Which), it is very powerful and fast! It can do things like Replace[L, {pattTest1->rhs1, pattTest2->rhs2, pattTest3->rhs3}, {1}] + module concept + even faster. $\endgroup$
    – imida k
    May 14 at 12:12

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.