2
$\begingroup$

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!

$\endgroup$
4
  • $\begingroup$ Try to use Evaluate somewhere you want it to be evaluated so that temporary variable is unnecessary $\endgroup$ Commented Mar 29, 2016 at 4:14
  • 3
    $\begingroup$ This is a great use case for With. $\endgroup$
    – march
    Commented Mar 29, 2016 at 4:21
  • 1
    $\begingroup$ 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. $\endgroup$
    – Jens
    Commented Mar 29, 2016 at 4:29
  • 1
    $\begingroup$ As of 10.1 one could also use MinMax and save one evaluation. $\endgroup$ Commented Mar 29, 2016 at 6:40

1 Answer 1

6
$\begingroup$

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.

$\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.