简体   繁体   English

在 Javascript 中遍历此链表时,这种 O(N) 方法是避免 while 循环的唯一方法吗?

[英]Is this O(N) approach the only way of avoiding a while loop when walking this linked list in Javascript?

I have a data structure that is essentially a linked list stored in state. It represents a stream of changes (patches) to a base object. It is linked by key, rather than by object reference, to allow me to trivially serialise and deserialise the state.我有一个数据结构,它本质上是一个存储在 state 中的链表。它表示对基数 object 的 stream 更改(补丁)。它通过键链接,而不是通过 object 引用链接,以允许我简单地序列化和反序列化state。

It looks like this:它看起来像这样:

const latest = 'id4' // They're actually UUIDs, so I can't sort on them (text here for clarity)
const changes = {
  id4: {patch: {}, previous: 'id3'},
  id3: {patch: {}, previous: 'id2'},
  id2: {patch: {}, previous: 'id1'},
  id1: {patch: {}, previous: undefined},
}

At some times, a user chooses to run an expensive calculation and results get returned into state. We do not have results corresponding to every change but only some.有时,用户选择运行昂贵的计算,结果返回到 state。我们没有对应于每个更改的结果,但只有一些。 So results might look like:所以结果可能看起来像:

const results = {
  id3: {performance: 83.6},
  id1: {performance: 49.6},
}

Given the changes array, I need to get the results closest to the tip of the changes list, in this case results.id3 .给定更改数组,我需要获得最接近更改列表顶端的结果,在本例中为results.id3

I've written a while loop to do this, and it's perfectly robust at present:我已经编写了一个 while 循环来执行此操作,目前它非常可靠:

    let id = latest
    let referenceId = undefined
    while (!!id) {
      if (!!results[id]) {
        referenceId = id
        id = undefined
      } else {
        id = changes[id].previous
      }
    }

The approach is O(N) but that's the pathological case: I expect a long changelist but with fairly frequent results updates, such that you'd only have to walk back a few steps to find a matching result.该方法是 O(N),但这是病态的情况:我希望有一个很长的更改列表,但结果更新相当频繁,这样您只需返回几步即可找到匹配的结果。

While loops can be vulnerable虽然循环可能很脆弱

Following the great work of Gene Krantz (read his book "Failure is not an option" to understand why NASA never use recursion:) I try to avoid using while loops in code bases.遵循 Gene Krantz 的伟大工作(阅读他的书“失败不是一种选择”以了解为什么 NASA 从不使用递归:)我尽量避免在代码库中使用 while 循环。 They tend to be susceptible to inadvertent mistakes.他们往往容易犯无意的错误。

For example, all that would be required to make an infinite loop here is to do delete changes.id1 .例如,在这里进行无限循环所需要做的就是执行delete changes.id1

So, I'd like to avoid that vulnerability and instead fail to retrieve any result, because not returning a performance value can be handled;因此,我想避免该漏洞,而不是检索任何结果,因为可以处理不返回性能值; but the user's app hanging is REALLY bad!但是用户的应用程序挂起真的很糟糕!

Other approaches I tried我尝试过的其他方法

Sorted array O(N)排序数组 O(N)

To avoid the while loop, I thought about sorting the changes object into an array ordered per the linked list, then simply looping through it.为了避免 while 循环,我考虑将changes object 排序到按链表排序的数组中,然后简单地循环遍历它。

The problem is that I have to traverse the whole changes list first to get the array in a sorted order, because I don't store an ordering key (it would violate the point of a linked list, because you could no longer do O(1) insert).问题是我必须先遍历整个更改列表才能按排序顺序获取数组,因为我没有存储排序键(这会违反链表的要点,因为你不能再做 O( 1)插入)。

It's not a heavy operation, to push an id onto an array, but is still O(N).将 id 压入数组并不是一项繁重的操作,但仍然是 O(N)。

The question问题

Is there a way of traversing this linked list without using a while loop, and without an O(N) approach to convert the linked list into a normal array?有没有一种方法可以在不使用 while 循环并且不使用 O(N) 方法将链表转换为普通数组的情况下遍历这个链表?

While writing out the question, I realised that rather than avoiding a while-loop entirely, I can add an execution count and an escape hatch which should be sufficient for the purpose.在写出问题时,我意识到与其完全避免 while 循环,不如添加一个执行计数和一个逃生舱口,这应该足以达到目的。

This solution uses Object.keys() which is strictly O(N) so not technically a correct answer to the question - but it is very fast.该解决方案使用Object.keys() ,它严格来说是 O(N) 所以在技术上不是问题的正确答案 - 但它非常快。

If I needed it faster, I could restructure changes as a map instead of a general object and access changes.size as per this answer如果我需要更快,我可以将changes重组为map而不是一般的 object 并根据此答案访问changes.size

    let id = latest
    let referenceId = undefined
    const maxLoops = Object.keys(changes).length
    let loop = 0
    while (!!id && loop < maxLoops) {
      loop++
      if (!!results[id]) {
        referenceId = id
        id = undefined
      } else {
        id = changes[id].previous
      }
    }

Since you only need to append at the end and possibly remove from the end, the required structure is a stack .由于最后只需要 append 并可能从末尾删除,因此所需的结构是stack In JavaScript the best data structure to implement a stack is an array -- using its push and pop features.在 JavaScript 中,实现堆栈的最佳数据结构是数组——使用其pushpop功能。

So then you could do things like this:那么你可以做这样的事情:

 const changes = []; function addChange(id, patch) { changes.push({id, patch}); } function findRecentMatch(changes, constraints) { for (let i = changes.length - 1; i >= 0; i--) { const {id} = changes[i]; if (constraints[id]) return id; } } // Demo addChange("id1", { data: 10 }); addChange("id2", { data: 20 }); addChange("id3", { data: 30 }); addChange("id4", { data: 40 }); const results = { id3: {performance: 83.6}, id1: {performance: 49.6}, } const referenceId = findRecentMatch(changes, results); console.log(referenceId); // id3

Depending on what you want to do with that referenceId you might want findRecentMatch to return the index in changes instead of the change-id itself.根据您想对该referenceId执行的操作,您可能希望findRecentMatch返回changes中的索引而不是更改 ID 本身。 This gives you the possibility to still retrieve the id , but also to clip the changes list to end at that "version" (ie as if you popped all the entries up to that point, but then in one operation).这使您有可能仍然检索id ,但也可以剪辑changes列表以在该“版本”结束(即,就好像您弹出了到该点的所有条目,但随后在一个操作中)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM