简体   繁体   English

走树结构时如何避免`shareReplay`

[英]How to avoid `shareReplay` when walking a tree structure

I am trying to rewrite a package manager using RxJS (the PM is pnpm and the PR is here ). 我想重写使用RxJS的软件包管理器(PM被PNPM和PR是在这里 )。

During the rewrite, I used lots of .shareReplay(Infinity) , which I've been told is bad (I am a beginner in reactive programming) 在重写期间,我使用了很多.shareReplay(Infinity) ,这被告知是不好的(我是反应式编程的初学者)

Could someone suggest an alternative how to rewrite code like the following, w/o using .shareReplay(Infinity) : 有人可以建议一种替代方法,如何使用.shareReplay(Infinity)重写以下代码:

'use strict'
const Rx = require('@reactivex/rxjs')

const nodes = [
  {id: 'a', children$: Rx.Observable.empty()},
  {id: 'b', children$: Rx.Observable.empty()},
  {id: 'c', children$: Rx.Observable.from(['a', 'b', 'd'])},
  {id: 'd', children$: Rx.Observable.empty()},
  {id: 'e', children$: Rx.Observable.empty()},
]

// I want this stream to be executed once, that is why the .shareReplay
const node$ = Rx.Observable.from(nodes).shareReplay(Infinity)

const children$ = node$.mergeMap(node => node.children$.mergeMap(childId => node$.single(node => node.id === childId)))

children$.subscribe(v => console.log(v))

The groupBy operator should work here. groupBy运算符应在此处工作。 Looking at the PR this might be a gross over-simplification, but here goes: 从PR来看,这可能是过分简化了,但这里有:

 
 
 
  
  'use strict' const Rx = require('@reactivex/rxjs') const nodes = [ {id: 'a', children$: Rx.Observable.empty()}, {id: 'b', children$: Rx.Observable.empty()}, {id: 'c', children$: Rx.Observable.from(['a', 'b', 'd'])}, {id: 'd', children$: Rx.Observable.empty()}, {id: 'e', children$: Rx.Observable.empty()}, ] Rx.Observable.from(nodes) // Group each of the nodes by its id .groupBy(node => node.id) // Flatten out each of the children by only forwarding children with the same id .flatMap(group$ => group$.single(childId => group$.key === childId)) .subscribe(v => console.log(v));
 
  

Edit: More difficult than I thought 编辑:比我想象的困难

Ok, so on my second read through I see that this requires more work than I initially thought so it can't be simplified so easy. 好的,因此,在我通读第二篇文章时,我发现它需要的工作比我最初想象的要多,因此不能简单地简化它。 Basically, you are going to have to choose between memory complexity and time complexity here since there isn't a magic bullet. 基本上,由于没有神奇的子弹,因此您将不得不在内存复杂度和时间复杂度之间进行选择。

From an optimization standpoint, if the initial source is just an Array then you can remove the shareReplay and it will work in the exact same way, because when subscribing to and ArrayObservable the only overhead is going to be iterating through the Array, there isn't really any extra cost to re-running the source. 从优化的角度来看,如果初始源只是一个Array,则可以删除shareReplay ,它将以完全相同的方式工作,因为订阅和ArrayObservable时,唯一的开销将是遍历Array,而没有重新运行源代码确实会产生任何额外费用。

Basically for this I think you can think of two dimensions, the number of nodes m and the average number of children n . 基本上,我认为您可以想到两个维度,节点数m和子级n的平均数。 In the speed optimized version you will end up having to run through the m twice and you will need to iterate through "n" nodes. 在速度优化版本中,您最终将不得不运行两次m ,并且需要遍历“ n”个节点。 Since you have m*n children the worst case would be that all of them are unique. 由于您有m * n个孩子,最坏的情况是他们都是唯一的。 Meaning you need to do (m + m*n) operations which simplifies to O(m*n) if I am not mistaken. 这意味着如果我没有记错的话,您需要执行(m + m*n)运算,简化为O(m*n) The draw back of this approach is that you need to have both a Map of (nodeId -> Node) and an Map for removing duplicate dependencies. 这种方法的缺点是您需要同时具有(nodeId-> Node)的Map和用于删除重复依赖项的Map。

'use strict'
const Rx = require('@reactivex/rxjs')

const nodes = [
  {id: 'a', children$: Rx.Observable.empty()},
  {id: 'b', children$: Rx.Observable.empty()},
  {id: 'c', children$: Rx.Observable.from(['a', 'b', 'd'])},
  {id: 'd', children$: Rx.Observable.empty()},
  {id: 'e', children$: Rx.Observable.empty()},
]

const node$ = Rx.Observable.from(nodes);

// Convert Nodes into a Map for faster lookup later
// Note this will increase your memory pressure.
const nodeMap$ = node$
  .reduce((map, node) => {
    map[node.id] = node;
    return map;
  });


node$
  // Flatten the children
  .flatMap(node => node.children$)
  // Emit only distinct children (you can remove this to relieve memory pressure
  // But you will still need to perform de-duping at some point.
  .distinct()
  // For each child find the associated node
  .withLatestFrom(nodeMap$, (childId, nodeMap) => nodeMap[childId])
  // Remove empty nodes, this could also be a throw if that is an error
  .filter(node => !!node)
  .subscribe(v => console.log(v));

The alternative is to use an approach similar to yours which focuses on memory pressure reduction at the cost of performance. 替代方法是使用与您类似的方法,该方法侧重于以性能为代价降低内存压力。 Note like I said you can basically just remove shareReplay if your source is an Array because all it is doing when it re-evaluates is re-iterate the array. 请注意,就像我说的那样,如果您的源是一个数组,则基本上可以删除shareReplay,因为重新评估时它所做的就是重新迭代该数组。 This removes the overhead of the additional Map. 这消除了附加Map的开销。 Though I think you still would need distinct to remove duplicates. 虽然我认为您仍然需要去除重复项。 The worst-case runtime complexity of this would be O(m^2*n) or simply O(m^2) if n is small, since you will need to iterate through all children and for each child you will also need to iterate through m again to find the matching node. 如果n小,则最坏情况下的运行时复杂度将为O(m^2*n)或简单地为O(m^2) ,因为您将需要遍历所有子代,并且对于每个子代,您还需要迭代再次通过m查找匹配的节点。

const node$ = Rx.Observable.from(nodes);
node$
  // Flatten the children
  .flatMap(node => node.children$)
  // You may still need a distinct to do the de-duping
  .flatMap(childId => node$.single(n => n.id === childId)));

I would say the first option is preferable in almost all cases, but I leave that to you to determine for your use case. 我想说第一种选择在几乎所有情况下都是可取的,但我将由您决定使用情况。 It may be that you establish some heuristic that chooses one algorithm over the other in certain circumstances. 在某些情况下,可能是您建立了一种启发式算法,以选择一种算法而不是另一种算法。

Sidenote: Sorry it wasn't as easy, but love pnpm so keep up the good work! 旁注:抱歉,这并不容易,但是喜欢pnpm,所以请继续努力!

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

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