簡體   English   中英

走樹結構時如何避免`shareReplay`

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

我想重寫使用RxJS的軟件包管理器(PM被PNPM和PR是在這里 )。

在重寫期間,我使用了很多.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))

groupBy運算符應在此處工作。 從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));
 
  

編輯:比我想象的困難

好的,因此,在我通讀第二篇文章時,我發現它需要的工作比我最初想象的要多,因此不能簡單地簡化它。 基本上,由於沒有神奇的子彈,因此您將不得不在內存復雜度和時間復雜度之間進行選擇。

從優化的角度來看,如果初始源只是一個Array,則可以刪除shareReplay ,它將以完全相同的方式工作,因為訂閱和ArrayObservable時,唯一的開銷將是遍歷Array,而沒有重新運行源代碼確實會產生任何額外費用。

基本上,我認為您可以想到兩個維度,節點數m和子級n的平均數。 在速度優化版本中,您最終將不得不運行兩次m ,並且需要遍歷“ n”個節點。 由於您有m * n個孩子,最壞的情況是他們都是唯一的。 這意味着如果我沒有記錯的話,您需要執行(m + m*n)運算,簡化為O(m*n) 這種方法的缺點是您需要同時具有(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));

替代方法是使用與您類似的方法,該方法側重於以性能為代價降低內存壓力。 請注意,就像我說的那樣,如果您的源是一個數組,則基本上可以刪除shareReplay,因為重新評估時它所做的就是重新迭代該數組。 這消除了附加Map的開銷。 雖然我認為您仍然需要去除重復項。 如果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)));

我想說第一種選擇在幾乎所有情況下都是可取的,但我將由您決定使用情況。 在某些情況下,可能是您建立了一種啟發式算法,以選擇一種算法而不是另一種算法。

旁注:抱歉,這並不容易,但是喜歡pnpm,所以請繼續努力!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM