记忆提供更少的表现,花费更多的时间

发布于 2025-02-05 20:23:51 字数 1984 浏览 2 评论 0 原文

我正在FreecodeCamp解决Euler项目。解决问题14时,我会使用递归,并尝试使用记忆来提高性能。但是,如果没有记忆,则需要更少的时间来执行,并且通过记忆需要更多的时间。备忘录实施是正确的吗?此代码有什么问题?有人可以解释吗?

//Memoized version of the longestCollatzSequence project euler
let t1 = Date.now();

// function recurseWrapper(n) {
//     let count = 0;
//     let memo = {}

//     function recurseCollatzSequence(n) {
//         if (n in memo) {
//             return memo[n];
//         } else {
//             if (n === 1) {
//                 return;
//             } else if (n % 2 === 0) {
//                 count++;
//                 memo[n / 2] = recurseCollatzSequence((n / 2))
//             } else {
//                 count++;
//                 memo[(3 * n) + 1] = recurseCollatzSequence(((3 * n) + 1))
//             }
//             return count
//         }

//     }
//     return recurseCollatzSequence(n);
// }

//Without memoization (better performance)

function recurseWrapper(n) {
    let count = 0;

    function recurseCollatzSequence(n) {

        if (n === 1) {
            return;
        } else if (n % 2 === 0) {
            count++;
            recurseCollatzSequence((n / 2))
        } else {
            count++;
            recurseCollatzSequence(((3 * n) + 1))
        }
        return count

    }
    return recurseCollatzSequence(n);
}


function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.log(longestCollatzSequence(54512))
let t2 = Date.now() - t1;
console.log(`time taken by first instruction ${t2}`);
console.log(longestCollatzSequence(900000));
let t3 = Date.now() - t1 - t2
console.log(`time taken by second instruction ${t3}`);
let t4 = Date.now() - t1 - t2 - t3
console.log(longestCollatzSequence(1000000))
console.log(`time taken by third instruction ${t4}`);

I was solving project euler in freecodecamp. When solving problem no 14 i use recursion and tried to increase the performance using memorization. But without memoization it is taking less time to execute and with memoization it is taking more time. Is memoization implementation is correct? What is wrong with this code? Can any one explain?

//Memoized version of the longestCollatzSequence project euler
let t1 = Date.now();

// function recurseWrapper(n) {
//     let count = 0;
//     let memo = {}

//     function recurseCollatzSequence(n) {
//         if (n in memo) {
//             return memo[n];
//         } else {
//             if (n === 1) {
//                 return;
//             } else if (n % 2 === 0) {
//                 count++;
//                 memo[n / 2] = recurseCollatzSequence((n / 2))
//             } else {
//                 count++;
//                 memo[(3 * n) + 1] = recurseCollatzSequence(((3 * n) + 1))
//             }
//             return count
//         }

//     }
//     return recurseCollatzSequence(n);
// }

//Without memoization (better performance)

function recurseWrapper(n) {
    let count = 0;

    function recurseCollatzSequence(n) {

        if (n === 1) {
            return;
        } else if (n % 2 === 0) {
            count++;
            recurseCollatzSequence((n / 2))
        } else {
            count++;
            recurseCollatzSequence(((3 * n) + 1))
        }
        return count

    }
    return recurseCollatzSequence(n);
}


function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.log(longestCollatzSequence(54512))
let t2 = Date.now() - t1;
console.log(`time taken by first instruction ${t2}`);
console.log(longestCollatzSequence(900000));
let t3 = Date.now() - t1 - t2
console.log(`time taken by second instruction ${t3}`);
let t4 = Date.now() - t1 - t2 - t3
console.log(longestCollatzSequence(1000000))
console.log(`time taken by third instruction ${t4}`);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心碎的声音 2025-02-12 20:23:51

从我对Collat​​z猜想的有限理解,从数字 n 开始时,使用您执行的所有操作,您再也不会看到相同的数字直到达到1(否则您最终会出现在无限循环中)。因此,您的备忘录对象将始终容纳永远不会匹配当前号码 n 的唯一键,因此如果(n中的n中的n中)永远不会是正确的。

实际上,您似乎想在循环中记忆结果 recursewrapper(),以便可以防止已经看到的计​​算结果。目前,您尚未这样做,因为您每次调用 recurseWrapper()时,要创建一个新的 Memo 对象,以删除所有记忆的值。取而代之的是,您可以返回通过备忘录对象关闭的内部辅助递归包装器功能,以便将一个备忘对象保留在递归包装器功能的所有调用中。

但是,即使如此,我们仍然会面临问题,因为如何计算。例如,如果您调用 recurseWrapper(n),并且需要10个迭代才能调用 recurseCollat​​zSequence(k),则返回的 recurseCollat​​zSequence(k)将为 10 加上计算 k 的Collat​​z序列所需的任何数字。如果我们记住这个数字,我们可能会面临问题。如果我们再次调用 recurseWrapper 在另一个号码 m recurseWrapper(m),这次需要20个迭代才能到达相同的呼叫 recurseCollat​​zSequence(k),我们将使用 k 的记忆价值。但是,此值对从 n k 进行的 10 还有一个额外的计数,而不仅仅是所花费的计数从 k 1 。结果,我们需要更改您的递归函数中计算 count 的方式,以便它是 pure 相同的结果。

正如文森特(Vincent)也在a ,您应该记住当前号码,即:备忘录[n] ,而不是您将要计算Collat​​z计数的数字(重新浏览时进行了记忆):

function createCollatzCounter() {
  const memo = {};
  return function recurseCollatzSequence(n) {
    if (n in memo) {
      return memo[n];
    } else {
      if (n === 1) {
        memo[n] = 0;
      } else if (n % 2 === 0) {
        memo[n] = 1 + recurseCollatzSequence(n / 2);
      } else {
        memo[n] = 1 + recurseCollatzSequence((3 * n) + 1);
      }
      return memo[n];
    }
  }
}

function longestCollatzSequence(n) {
  let max = 0;
  let startNum = 0;
  const recurseWrapper = createCollatzCounter();
  for (let i = n; i > 1; i--) {
    let changeMax = recurseWrapper(i)
    if (changeMax > max) {
      max = changeMax;
      startNum = i;
    }
  }
  return startNum;
}

console.time("First");
console.log(longestCollatzSequence(54512));
console.timeEnd("First");
console.time("Second");
console.log(longestCollatzSequence(900000));
console.timeEnd("Second");
console.time("Third");
console.log(longestCollatzSequence(1000000));
console.timeEnd("Third");

相比之下,以下显示没有备忘录的时间:

//Without memoization (better performance)

function recurseWrapper(n) {
    let count = 0;

    function recurseCollatzSequence(n) {

        if (n === 1) {
            return;
        } else if (n % 2 === 0) {
            count++;
            recurseCollatzSequence((n / 2))
        } else {
            count++;
            recurseCollatzSequence(((3 * n) + 1))
        }
        return count

    }
    return recurseCollatzSequence(n);
}


function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.time("First");
console.log(longestCollatzSequence(54512))
console.timeEnd("First");
console.time("Second");
console.log(longestCollatzSequence(900000));
console.timeEnd("Second");
console.time("Third");
console.log(longestCollatzSequence(1000000));
console.timeEnd("Third");

From my limited understanding of the collatz conjecture, when starting at a number n, with all of the operations that you do, you should never see the same number again until you reach 1 (otherwise you would end up in an infinite loop). So your memo object will always hold unique keys that will never match the current number n, so if (n in memo) will never be true.

It actually seems that you want to memoize your results for further calls recurseWrapper() within your loop, so that you can prevent computing results you've already seen. At the moment you are not doing that, as you are creating a new memo object each time you call recurseWrapper(), removing all of the memoized values. You can instead return your inner auxiliary recursive wrapper function that closes over the memo object so that you keep the one memo object for all calls of the recursive wrapper function.

But even then we will still face issues, because of how the count is being calculated. For example, if you call recurseWrapper(n) and it takes 10 iterations to call recurseCollatzSequence(k), the returned count of recurseCollatzSequence(k) is going to be 10 plus whatever number it takes to compute the Collatz sequence for k. If we memoize this number, we can face issues. If we again call recurseWrapper on another number m, recurseWrapper(m), and this time it takes 20 iterations to get to the same call of recurseCollatzSequence(k), we will use our memoized value for k. But this value holds an additional count for the 10 that it took to get from n to k, and not just the count that it took to get from k to 1. As a result, we need to change how you're computing count in your recursive function so that it is pure, so that calling a function with the same arguments always produces the same result.

As Vincent also points out in a comment, you should be memoizing the current number, ie: memo[n] and not the number you're about to compute the Collatz count for (that memoization is done when you recurse):

function createCollatzCounter() {
  const memo = {};
  return function recurseCollatzSequence(n) {
    if (n in memo) {
      return memo[n];
    } else {
      if (n === 1) {
        memo[n] = 0;
      } else if (n % 2 === 0) {
        memo[n] = 1 + recurseCollatzSequence(n / 2);
      } else {
        memo[n] = 1 + recurseCollatzSequence((3 * n) + 1);
      }
      return memo[n];
    }
  }
}

function longestCollatzSequence(n) {
  let max = 0;
  let startNum = 0;
  const recurseWrapper = createCollatzCounter();
  for (let i = n; i > 1; i--) {
    let changeMax = recurseWrapper(i)
    if (changeMax > max) {
      max = changeMax;
      startNum = i;
    }
  }
  return startNum;
}

console.time("First");
console.log(longestCollatzSequence(54512));
console.timeEnd("First");
console.time("Second");
console.log(longestCollatzSequence(900000));
console.timeEnd("Second");
console.time("Third");
console.log(longestCollatzSequence(1000000));
console.timeEnd("Third");

In comparison, the below shows times without memo:

//Without memoization (better performance)

function recurseWrapper(n) {
    let count = 0;

    function recurseCollatzSequence(n) {

        if (n === 1) {
            return;
        } else if (n % 2 === 0) {
            count++;
            recurseCollatzSequence((n / 2))
        } else {
            count++;
            recurseCollatzSequence(((3 * n) + 1))
        }
        return count

    }
    return recurseCollatzSequence(n);
}


function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.time("First");
console.log(longestCollatzSequence(54512))
console.timeEnd("First");
console.time("Second");
console.log(longestCollatzSequence(900000));
console.timeEnd("Second");
console.time("Third");
console.log(longestCollatzSequence(1000000));
console.timeEnd("Third");

夏至、离别 2025-02-12 20:23:51

这是该解决方案的简化版本,在纪念和递归周围具有最小的噪声。

let memo = {};
function collatzSequence(n) {
    if (! (n in memo)) {
        if (n == 1) {
            memo[n] = 1;
        }
        else if ((n % 2) === 0) {
            memo[n] = 1 + collatzSequence(n/2);
        } else {
            memo[n] = 1 + collatzSequence(3*n + 1);
        }
    }
    return memo[n];
}

function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = collatzSequence(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.log(longestCollatzSequence(14))

而且,如果您愿意接受一些高尔夫球,这仍然是第一个功能的较短版本。

let memo = {1: 1};
function collatzSequence(n) {
    if (!(n in memo)) {
        memo[n] = 1 + collatzSequence(0 === n%2 ? n/2 : 3*n+1);
    }
    return memo[n];
}

之所以重要的原因与我们的想法有关。只要代码自然而然地读取,写作和思考它需要多长时间才与它的时间直接相关。 (在许多地方都发现这在多种语言中都是真实的。我知道当然有。)因此,学会对代码进行更有效的思考将使您更快地编写。

这就是为什么在不谈论所使用的技术的情况下学习如何使用技术会使您在这种技术方面更好的原因。

Here is a streamlined version of the solution with minimal noise around memoization and recursion.

let memo = {};
function collatzSequence(n) {
    if (! (n in memo)) {
        if (n == 1) {
            memo[n] = 1;
        }
        else if ((n % 2) === 0) {
            memo[n] = 1 + collatzSequence(n/2);
        } else {
            memo[n] = 1 + collatzSequence(3*n + 1);
        }
    }
    return memo[n];
}

function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = collatzSequence(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}

console.log(longestCollatzSequence(14))

And if you're willing to accept a bit of golf, here is a shorter version still of the first function.

let memo = {1: 1};
function collatzSequence(n) {
    if (!(n in memo)) {
        memo[n] = 1 + collatzSequence(0 === n%2 ? n/2 : 3*n+1);
    }
    return memo[n];
}

The reason why this matters has to do with how we think. As long as code reads naturally to us, how long it takes to write and think about it is directly correlated with how long it is. (This has been found to be true across a variety of languages in many places. I know that Software Estimation: Demystifying the Black Art certainly has it.) Therefore learning to think more efficiently about code will make it faster for you to write.

And that is why learning how to use techniques without talking about the technique you're using makes you better at that technique.

三人与歌 2025-02-12 20:23:51

花了一些时间弄清楚,我找到了一个工作解决方案。
以下代码提高了时间复杂性。谢谢大家帮助我。

//Memoized version of the longestCollatzSequence project euler
let memo = {}

function recurseWrapper(n) {
    let count = 0;
    if (n in memo) {
        return memo[n];
    } else {
        function recurseCollatzSequence(n) {
            if (n === 1) {
                return;
            } else if (n % 2 === 0) {
                count++;
                recurseCollatzSequence((n / 2))
            } else {
                count++;
                recurseCollatzSequence(((3 * n) + 1))
            }

            return count

        }
        let c = recurseCollatzSequence(n);
        memo[n] = c;
        return c;
    }

}

function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}
longestCollatzSequence(14)

After spending some time figuring out I found a working solution.
The code below improved time complexity. Thanks all for helping me.

//Memoized version of the longestCollatzSequence project euler
let memo = {}

function recurseWrapper(n) {
    let count = 0;
    if (n in memo) {
        return memo[n];
    } else {
        function recurseCollatzSequence(n) {
            if (n === 1) {
                return;
            } else if (n % 2 === 0) {
                count++;
                recurseCollatzSequence((n / 2))
            } else {
                count++;
                recurseCollatzSequence(((3 * n) + 1))
            }

            return count

        }
        let c = recurseCollatzSequence(n);
        memo[n] = c;
        return c;
    }

}

function longestCollatzSequence(n) {
    let max = 0;
    let startNum = 0;
    for (let i = n; i > 1; i--) {
        let changeMax = recurseWrapper(i)
        if (changeMax > max) {
            max = changeMax;
            startNum = i;
        }
    }
    return startNum;
}
longestCollatzSequence(14)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文