How to force evaluation inside the body of a pure function?

For example, I’m writing a function to scale my list of numbers into a specific range. It goes like

ScaleTo[data_, range_] := (# – Min[data]) (Last[range] – First[range]) / (Max[data] – Min[data]) & /@ data

But as I found out, the value of Last[range] – First[range], Max[data] – Min[data] will be evaluated for each element being scaled although the values are always the same. Using a temporary variable to store the function would solve the problem

ScaleTo[data_, range_] := Module[{ScaleFunc},
ScaleFunc[x_] = (x – Min[data]) (Last[range] – First[range])/(Max[data] – Min[data]);
ScaleFunc /@ data]

Is there a better way? Besides, if I substitute the second line with

ScaleFunc = (# – Min[data]) (Last[range] – First[range]) / (Max[data] – Min[data]) &;

It wouldn’t be evaluated until called. Why is that? Thanks!

=================

  

 

Try to use Evaluate somewhere you want it to be evaluated so that temporary variable is unnecessary
– LCFactorization
Mar 29 at 4:14

3

 

This is a great use case for With.
– march
Mar 29 at 4:21

1

 

It’s trivial in this case (not to mention there is a built-in function Rescale): ScaleTo[data_, range_] := (data – Min[data]) (Last[range] – First[range])/(Max[data] – Min[data]). So you don’t need a pure function at all.
– Jens
Mar 29 at 4:29

1

 

As of 10.1 one could also use MinMax and save one evaluation.
– b.gatessucks
Mar 29 at 6:40

=================

1 Answer
1

=================

Starting with the last line, when you define

ScaleFunc = (# –
Min[data]) (Last[range] – First[range])/(Max[data] – Min[data]) &;

the HoldAll attribute of the & (see Function) prevents data and range to receive any values. As soon as you then apply ScaleFunc to an argument, say ScaleFunc[Range[10]], the functions Min etc. in the function body do evaluate their arguments because they themselves as usual.

This HoldAll effect doesn’t occur when you use the definition

Clear[ScaleFunc,x];
ScaleFunc[x_] = (x – Min[data]) (Last[range] – First[range])/(Max[data] – Min[data]);

as you do in the Module. There, it would actually have been safer to use SetDelayed instead of Set because x is a global variable as written (I added the Clear just because I pulled things apart from your Module for the sake of discussion).

Since you want to use a pure function somehow, we should try to modify the ingredients of your Module accordingly. Here are two suggestions that get by without Module but use functions in rather different ways:

ScaleTo[range_] :=
With[{del = Last[range] – First[range]}, (# – Min[#]) del/(Max[#] – Min[#]) &]

ScaleTo[range_] := Function[d, (d – Min[d]) #/(Max[d] – Min[d])] &[
Last[range] – First[range]]

Both approaches separate the range from the second argument because range contains parameters that we don’t want to be fed into a re-evaluation every time the second argument (data) is supplied.

The first version uses With as suggested by @march. It substitutes the calculated value of del when you call ScaleFunc with a range argument. The result is a function that can be applied to data.

The same result is also achieved in the second version, but there I use Function to define it. In addition, the range information is injected into the definition of that function by another (pure) function (that’s what With did in the previous version).

The explanation why we need With or a sequence of two functions is again the HoldAll attribute of Function. With gets around this attribute because it makes literal substitutions inside its scope, and the # inside Function together with & allow evaluation of Last[range]-First[range] before defining the Function, because that intermediate step is calculated outside the function body.