【问题标题】:Memoization of a recursive function in JavascriptJavascript中递归函数的记忆
【发布时间】:2020-02-20 10:03:48
【问题描述】:

我有这个递归函数,它接收一组卡片并找到特定卡片的索引:

const getCardIndex = (deck, fullCardName) => fullCardName === deck[0] ? 52 - deck.length : getCardIndex(deck.splice(1), fullCardName)

const deck = 'Ace of Hearts, Ace of Diamonds, Ace of Clubs, Ace of Spades, Two of Hearts, Two of Diamonds, Two of Clubs, Two of Spades, Three of Hearts, Three of Diamonds, Three of Clubs, Three of Spades, Four of Hearts, Four of Diamonds, Four of Clubs, Four of Spades, Five of Hearts, Five of Diamonds, Five of Clubs, Five of Spades, Six of Hearts, Six of Diamonds, Six of Clubs, Six of Spades, Seven of Hearts, Seven of Diamonds, Seven of Clubs, Seven of Spades, Eight of Hearts, Eight of Diamonds, Eight of Clubs, Eight of Spades, Nine of Hearts, Nine of Diamonds, Nine of Clubs, Nine of Spades, Ten of Hearts, Ten of Diamonds, Ten of Clubs, Ten of Spades, Jack of Hearts, Jack of Diamonds, Jack of Clubs, Jack of Spades, Queen of Hearts, Queen of Diamonds, Queen of Clubs, Queen of Spades, King of Hearts, King of Diamonds, King of Clubs, King of Spades'.split(', ')

const result = getCardIndex(deck, 'King of Spades')

console.log(result)

有什么方法可以加快速度,即使用记忆?

【问题讨论】:

  • 记忆化通常会增加内存使用,但不会减少 - 它需要存储更多持久性数据
  • @CertainPerformance 请原谅,错误的问题,现在更正。
  • 为什么这甚至是一个递归函数? deck.findIndex(card => card === 'King of Spades')
  • deck.indexOf('King of Spades') 有什么问题?

标签: javascript recursion memoization


【解决方案1】:

正如 cmets 所建议的,没有理由递归地执行此操作。 indexOf 很好地解决了这个问题。

但是,如果您决定编写递归解决方案,那么您的技术就会出现很大问题。你正在破坏你试图搜索的对象!

Array.prototype.splice 具有破坏性。它改变了它工作的数组。最后,您将拥有一个索引和一副几乎是空的纸牌!

这几乎是巧合。如果您可能打算使用slice,那将有效且不会出现此问题。 slice 只是为您提供从一个索引开始并在另一个索引(或数组末尾)结束的数组副本。splice 做得更多。它删除元素的子列表,插入其他元素并返回已删除的元素。如果你碰巧只用一个起始索引调用它,它会删除数组的其余部分并返回它。所以 调用 deck.slice(1)deck.splice(1) 碰巧返回了相同的东西,但第二个调用也会从您的数组中删除所有返回的项目。

因此,对您的功能的最快修复就是这样:

const getCardIndex = (deck, fullCardName) => 
  fullCardName === deck[0] 
    ? 52 - deck.length 
    : getCardIndex (deck.slice(1), fullCardName)

但老实说,这没什么意义。它可以工作,但是在每次递归调用时,它都会构建一个新数组,比以前的版本短。对于简单的搜索来说,这会占用大量内存。

所以这是另一种只在索引上递归的技术:

const getCardIndex = (deck, fullCardName, idx = 0) =>
  idx >= deck.length 
    ? -1
    : deck [idx] == fullCardName
      ? idx
      : getCardIndex (deck, fullCardName, idx + 1)

但是请注意,除了变量和函数的名称之外,这里没有任何特定于套牌和卡片的内容。因此我们可以将其转换为更通用的函数,如下所示:

const getIndex = (xs, x, idx = 0) =>
  idx >= xs.length 
    ? -1
    : xs [idx] == x
      ? idx
      : getIndex (xs, x, idx + 1)

我们有一个递归解决方案来查找任意数组中的值的索引。

同样,使用此功能的理由很少。这是学习递归的合理练习,但仅此而已。请改用indexOf

【讨论】:

  • 绝对惊人的答案。谢谢!
【解决方案2】:

对于一个预先识别解决方案的解决方案(O(52) = O(1)),然后可以在单个操作中查找解决方案,抛弃递归函数,将卡组变成一个对象,其键是卡片名称和values 是卡片索引,然后只需查找对象上的卡片属性:

const deck = 'Ace of Hearts, Ace of Diamonds, Ace of Clubs, Ace of Spades, Two of Hearts, Two of Diamonds, Two of Clubs, Two of Spades, Three of Hearts, Three of Diamonds, Three of Clubs, Three of Spades, Four of Hearts, Four of Diamonds, Four of Clubs, Four of Spades, Five of Hearts, Five of Diamonds, Five of Clubs, Five of Spades, Six of Hearts, Six of Diamonds, Six of Clubs, Six of Spades, Seven of Hearts, Seven of Diamonds, Seven of Clubs, Seven of Spades, Eight of Hearts, Eight of Diamonds, Eight of Clubs, Eight of Spades, Nine of Hearts, Nine of Diamonds, Nine of Clubs, Nine of Spades, Ten of Hearts, Ten of Diamonds, Ten of Clubs, Ten of Spades, Jack of Hearts, Jack of Diamonds, Jack of Clubs, Jack of Spades, Queen of Hearts, Queen of Diamonds, Queen of Clubs, Queen of Spades, King of Hearts, King of Diamonds, King of Clubs, King of Spades'.split(', ')
const deckObj = Object.fromEntries(deck.map((str, i) => [str, i]));


console.log(deckObj['King of Spades'])
console.log(deckObj['Ace of Diamonds'])
console.log(deckObj['Queen of Diamonds'])

【讨论】:

  • 我假设deck 有 52 张卡(假设它在 OP 的代码中是硬编码的),并且O(52)O(1) 相同。对象构造完成后,对对象的属性查找只需一次操作。
  • 按照这个逻辑,由于一副牌有 52 张牌,findIndex/indexOf 方法也是 O(1)。
猜你喜欢
  • 2012-11-26
  • 2019-03-23
  • 2014-10-31
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 2021-11-17
  • 1970-01-01
相关资源
最近更新 更多