【问题标题】:How to "remember" previously calculated values in an expensive recursive function in react?如何在反应中“记住”昂贵的递归函数中先前计算的值?
【发布时间】:2021-12-14 01:29:51
【问题描述】:

假设我有一个这样的递归回调(但更复杂):

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

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

有没有办法存储以前计算的值,这样如果在重复索引上调用函数,就可以跳过递归?

const value1 = weightedFactorial(60) 
// Calculate recursively

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

我试图保持一个具有已知值的状态,但我似乎陷入了一个循环,可能是因为它需要成为回调的依赖项,

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]);

【问题讨论】:

  • 不要使用状态。使用参考。并使其成为一个简单的数组或Map,您可以在其中按索引查找。
  • 对于useCallback() 来说似乎不是一个好的用例(因为首先不能保证所有以前记住的值仍然可以访问)。这似乎是一项动态编程任务,但如果没有看到您的实际回调,就无法提出一些有意义的解决方案。
  • @Bergi - 谢谢,这正是我需要的提示。当某些依赖项发生更改时,我还必须添加一个 useEffect 来重置地图,但它运行良好。请随时提交答案,以便我接受。
  • @YevgenGorbunkov - 这似乎是我的示例代码不起作用的原因,但可以使用 ref

标签: javascript reactjs recursion react-hooks


【解决方案1】:

memoisation 存储结果的最简单方法是Map,或者如果您的函数参数是正整数,则使用数组:

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>;
}

如果您多次调用factorial,这将很好地工作并且不会多次计算阶乘。但是,它仍然会在每次渲染函数组件时重做计算。要在重新渲染中保留结果,请将 results 变量从函数中移出到模块范围内,或将其放在 reference 中(每个已安装的组件将存储一次):

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>;
}

但你有一个更复杂的情况:weightedFactorial 还取决于weight 状态,而不仅仅是它的参数1。您可以在每次weight 更改时重置引用,但这很复杂且容易出错。

改为使用类似于useCallback 的方法,将结果存储与“回调”一起切换。代替useCallback,使用useMemo 并从中返回一个闭包:

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

1:当然,您可以将weightn 都视为参数,并使用a standard approach for memoising functions with multiple arguments(另见here)。然后结果可以再次静态存储或存储在引用中。

【讨论】:

  • 写得真好,非常感谢。
【解决方案2】:

您可以使用React.useMemo 在第一次渲染或依赖项更改时计算值。但也许您指的是memoization,以便先前计算的值不需要其他计算。 https://www.30secondsofcode.org/js/s/memoizehttps://www.digitalocean.com/community/tutorials/js-understanding-recursion

您可以结合useMemomemoization 来实现您所需要的。

【讨论】:

  • 这不是我的意思,我需要将中间步骤存储在递归函数中,以便对该函数的后续调用不需要经过递归并且可以简单地查找。我最终使用了一个反应参考 btw
  • 这正是记忆函数的作用。它将所有中间计算存储在其内部缓存中,当您再次调用它时,如果在缓存中找到已经计算的值。
  • 啊,我明白了。你完全正确
猜你喜欢
  • 1970-01-01
  • 2016-08-11
  • 2018-10-07
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 2016-10-16
  • 2017-04-19
相关资源
最近更新 更多