77 */
88
99import { TrackByFunction } from '../change_detection' ;
10+ import { assertNotSame } from '../util/assert' ;
1011
1112/**
1213 * A type representing the live collection to be reconciled with any new (incoming) collection. This
@@ -86,7 +87,7 @@ function valuesMatching<V>(
8687export function reconcile < T , V > (
8788 liveCollection : LiveCollection < T , V > , newCollection : Iterable < V > | undefined | null ,
8889 trackByFn : TrackByFunction < V > ) : void {
89- let detachedItems : MultiMap < unknown , T > | undefined = undefined ;
90+ let detachedItems : UniqueValueMultiKeyMap < unknown , T > | undefined = undefined ;
9091 let liveKeysInTheFuture : Set < unknown > | undefined = undefined ;
9192
9293 let liveStartIdx = 0 ;
@@ -148,7 +149,7 @@ export function reconcile<T, V>(
148149
149150 // Fallback to the slow path: we need to learn more about the content of the live and new
150151 // collections.
151- detachedItems ??= new MultiMap ( ) ;
152+ detachedItems ??= new UniqueValueMultiKeyMap ( ) ;
152153 liveKeysInTheFuture ??=
153154 initLiveItemsInTheFuture ( liveCollection , liveStartIdx , liveEndIdx , trackByFn ) ;
154155
@@ -197,7 +198,7 @@ export function reconcile<T, V>(
197198 liveStartIdx ++ ;
198199 newIterationResult = newCollectionIterator . next ( ) ;
199200 } else {
200- detachedItems ??= new MultiMap ( ) ;
201+ detachedItems ??= new UniqueValueMultiKeyMap ( ) ;
201202 liveKeysInTheFuture ??=
202203 initLiveItemsInTheFuture ( liveCollection , liveStartIdx , liveEndIdx , trackByFn ) ;
203204
@@ -246,8 +247,9 @@ export function reconcile<T, V>(
246247}
247248
248249function attachPreviouslyDetached < T , V > (
249- prevCollection : LiveCollection < T , V > , detachedItems : MultiMap < unknown , T > | undefined ,
250- index : number , key : unknown ) : boolean {
250+ prevCollection : LiveCollection < T , V > ,
251+ detachedItems : UniqueValueMultiKeyMap < unknown , T > | undefined , index : number ,
252+ key : unknown ) : boolean {
251253 if ( detachedItems !== undefined && detachedItems . has ( key ) ) {
252254 prevCollection . attach ( index , detachedItems . get ( key ) ! ) ;
253255 detachedItems . delete ( key ) ;
@@ -257,7 +259,8 @@ function attachPreviouslyDetached<T, V>(
257259}
258260
259261function createOrAttach < T , V > (
260- liveCollection : LiveCollection < T , V > , detachedItems : MultiMap < unknown , T > | undefined ,
262+ liveCollection : LiveCollection < T , V > ,
263+ detachedItems : UniqueValueMultiKeyMap < unknown , T > | undefined ,
261264 trackByFn : TrackByFunction < unknown > , index : number , value : V ) {
262265 if ( ! attachPreviouslyDetached ( liveCollection , detachedItems , index , trackByFn ( index , value ) ) ) {
263266 const newItem = liveCollection . create ( index , value ) ;
@@ -277,43 +280,78 @@ function initLiveItemsInTheFuture<T>(
277280 return keys ;
278281}
279282
280- class MultiMap < K , V > {
281- private map = new Map < K , Array < V > > ( ) ;
283+ /**
284+ * A specific, partial implementation of the Map interface with the following characteristics:
285+ * - allows multiple values for a given key;
286+ * - maintain FIFO order for multiple values corresponding to a given key;
287+ * - assumes that all values are unique.
288+ *
289+ * The implementation aims at having the minimal overhead for cases where keys are _not_ duplicated
290+ * (the most common case in the list reconciliation algorithm). To achieve this, the first value for
291+ * a given key is stored in a regular map. Then, when more values are set for a given key, we
292+ * maintain a form of linked list in a separate map. To maintain this linked list we assume that all
293+ * values (in the entire collection) are unique.
294+ */
295+ export class UniqueValueMultiKeyMap < K , V > {
296+ // A map from a key to the first value corresponding to this key.
297+ private kvMap = new Map < K , V > ( ) ;
298+ // A map that acts as a linked list of values - each value maps to the next value in this "linked
299+ // list" (this only works if values are unique). Allocated lazily to avoid memory consumption when
300+ // there are no duplicated values.
301+ private _vMap : Map < V , V > | undefined = undefined ;
282302
283303 has ( key : K ) : boolean {
284- const listOfKeys = this . map . get ( key ) ;
285- return listOfKeys !== undefined && listOfKeys . length > 0 ;
304+ return this . kvMap . has ( key ) ;
286305 }
287306
288307 delete ( key : K ) : boolean {
289- const listOfKeys = this . map . get ( key ) ;
290- if ( listOfKeys !== undefined ) {
291- listOfKeys . shift ( ) ;
292- return true ;
308+ if ( ! this . has ( key ) ) return false ;
309+
310+ const value = this . kvMap . get ( key ) ! ;
311+ if ( this . _vMap !== undefined && this . _vMap . has ( value ) ) {
312+ this . kvMap . set ( key , this . _vMap . get ( value ) ! ) ;
313+ this . _vMap . delete ( value ) ;
314+ } else {
315+ this . kvMap . delete ( key ) ;
293316 }
294- return false ;
317+
318+ return true ;
295319 }
296320
297321 get ( key : K ) : V | undefined {
298- const listOfKeys = this . map . get ( key ) ;
299- return listOfKeys !== undefined && listOfKeys . length > 0 ? listOfKeys [ 0 ] : undefined ;
322+ return this . kvMap . get ( key ) ;
300323 }
301324
302325 set ( key : K , value : V ) : void {
303- // if value is array, they we always store it as [value].
304- if ( ! this . map . has ( key ) ) {
305- this . map . set ( key , [ value ] ) ;
306- return ;
326+ if ( this . kvMap . has ( key ) ) {
327+ let prevValue = this . kvMap . get ( key ) ! ;
328+ ngDevMode &&
329+ assertNotSame (
330+ prevValue , value , `Detected a duplicated value ${ value } for the key ${ key } ` ) ;
331+
332+ if ( this . _vMap === undefined ) {
333+ this . _vMap = new Map ( ) ;
334+ }
335+
336+ const vMap = this . _vMap ;
337+ while ( vMap . has ( prevValue ) ) {
338+ prevValue = vMap . get ( prevValue ) ! ;
339+ }
340+ vMap . set ( prevValue , value ) ;
341+ } else {
342+ this . kvMap . set ( key , value ) ;
307343 }
308- // THINK: this allows duplicate values, but I guess this is fine?
309- // Is the existing key an array or not?
310- this . map . get ( key ) ?. push ( value ) ;
311344 }
312345
313346 forEach ( cb : ( v : V , k : K ) => void ) {
314- for ( const [ key , values ] of this . map ) {
315- for ( const value of values ) {
316- cb ( value , key ) ;
347+ for ( let [ key , value ] of this . kvMap ) {
348+ cb ( value , key ) ;
349+ if ( this . _vMap !== undefined ) {
350+ const vMap = this . _vMap ;
351+ while ( vMap . has ( value ) ) {
352+ value = vMap . get ( value ) ! ;
353+ cb ( value , key ) ;
354+ }
317355 }
318356 }
319357 }
0 commit comments