Home > Blockchain >  How to "remember" previously calculated values in an expensive recursive function in react
How to "remember" previously calculated values in an expensive recursive function in react

Time:10-29

Suppose I have a recursive callback such as this (but more complex):

const weightedFactorial = useCallback(n => {
  if (n === 0) {
    return 1;
  }

  return weight * n * weightedFactorial(n - 1);
},[weight]);

Is there a way to store previously calculated values, such that if the function is called on a repeated index, it is possible to skip the recursion?

const value1 = weightedFactorial(60) 
// Calculate recursively

const value2 = weightedFactorial(30) 
// This value should already be known 
// and the calculation should be skipped

I've tried to keep a state with known values but I seem to get stuck in a loop, probably because it needs to be a dependency of the callback,

const [knownValues, setKnownValues] = useState([{ n: 0, value: 1 }]);
const weightedFactorial = useCallback(n => {
  const known = knownValues.find(known => known.n === n);
  if (known?.value) {
    return known.value;
  }

  const newValue = weight * n * weightedFactorial(n - 1);
  setKnownValues(knownValues => [...knownValues, { n, value: newValue }]);
  return newValue;
},[knownValues, weight]);

CodePudding user response:

You can use React.useMemo to calculate the value on the first render or when a dependency changes. But maybe you are referring to memoization so that a previously calculate value doesn't need other computation. https://www.30secondsofcode.org/js/s/memoize or https://www.digitalocean.com/community/tutorials/js-understanding-recursion

You can combine both useMemo and memoization to achieve what you need.

CodePudding user response:

The easiest way to store results for memoisation is a Map, or an array if your function parameter is a positive integer:

function FactorialExamples(props) {
  const results = [1];
  const factorial = n => {
    if (n in results) return results[n]; // also takes care of our base case n==0
    return results[n] = n * factorial(n-1);
  };
  
  return <p>
    5!: { factorial(5) }
    <br />
    10!: { factorial(10) }
  </p>;
}

This will work nicely and not compute factorials more than once if you call factorial multiple times. However, it will still redo the computation on every rendering of your function component. To persist the results across re-renders, move the results variable out of the function into the module scope, or put it inside a reference (that will be stored once per mounted component):

function Factorial(props) {
  const results = useRef([1]).current;

  const factorial = n => {
    if (n in results) return results[n];
    return results[n] = n * factorial(n-1);
  };

  return <p>
    {props.n}!: { factorial(props.n) }
  </p>;
}

function Demo() {
  const [value, setValue] = useState(0);
  return <div>
    <input type="number" value={value} onInput=(e => setValue(e.currentTarget.valueAsNumber)} min="0" />
    <Factorial n={value} />
  </div>;
}

But you have a more complicated case: the weightedFactorial also depends on a weight state, not just on its argument1. You could reset the reference every time the weight changes, but that's complicated and error-prone.

Instead, use an approach similar to your useCallback, that switches out the results store together with the "callback". Instead of useCallback, use useMemo and return a closure from it:

const weightedFactorial = useMemo(() => {
  const results = [1];
  return n => {
    if (n in results) return results[n]; 
    return weight * n * weightedFactorial(n - 1);
  };
}, [weight]);

1: Of course, you could just treat both weight and n as arguments, and use a standard approach for memoising functions with multiple arguments (see also here). Then the results could again be stored statically or in a reference.

  • Related