Advertisement
crutch12

DataComparer.ts

May 14th, 2020
318
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Class for compare previous/next arrays of data, detect enter/update/exit info
  2. // Inspired by https://github.com/d3/d3-selection#selection_join
  3.  
  4. export type KeyFunction<T> = (datum: T) => string | symbol;
  5. export type DiffFunction<T> = (datum1: T, datum2: T) => boolean;
  6.  
  7. export type UpdateInfo<T> = { before: T, after: T };
  8.  
  9. export class DataComparer<T> {
  10.   private currentData: T[];
  11.   private currentDataByKey: Map<string | symbol, T>;
  12.  
  13.   private keyFn: KeyFunction<T>;
  14.   private diffFn: DiffFunction<T>;
  15.  
  16.   private enter: T[];
  17.   private update: UpdateInfo<T>[];
  18.   private exit: T[];
  19.  
  20.   constructor() {
  21.     this.keyFn = datum => JSON.stringify(datum);
  22.     this.diffFn = datum => Boolean(datum);
  23.  
  24.     this.currentData = [];
  25.     this.currentDataByKey = this.getDataByKey(this.currentData);
  26.  
  27.     this.enter = [];
  28.     this.update = [];
  29.     this.exit = [];
  30.   }
  31.  
  32.   // Overloads
  33.   public key(): KeyFunction<T>;
  34.   public key(keyFn: KeyFunction<T>): this;
  35.  
  36.   // @NOTE: Set key function (or get current keyFn)
  37.   public key(keyFn?: KeyFunction<T>) {
  38.     if (!keyFn) return this.keyFn;
  39.  
  40.     this.keyFn = keyFn;
  41.     this.currentDataByKey = this.getDataByKey(this.currentData);
  42.     return this;
  43.   }
  44.  
  45.   // Overloads
  46.   public diff(): DiffFunction<T>;
  47.   public diff(diffFn: DiffFunction<T>): this;
  48.  
  49.   // @NOTE: Set diff function (how to compare two datums) (or get current diffFn)
  50.   public diff(diffFn?: DiffFunction<T>) {
  51.     if (!diffFn) return this.diffFn;
  52.  
  53.     this.diffFn = diffFn;
  54.     return this;
  55.   }
  56.  
  57.   // @NOTE: Update data and compare
  58.   public data(data: T[]) {
  59.     const dataByKey = this.getDataByKey(data);
  60.  
  61.     const { enter, update, exit } = this.compare(this.currentDataByKey, dataByKey);
  62.  
  63.     this.enter = enter;
  64.     this.update = update;
  65.     this.exit = exit;
  66.  
  67.     this.currentData = data;
  68.     this.currentDataByKey = dataByKey;
  69.  
  70.     return this;
  71.   }
  72.  
  73.   // @NOTE: Get comparison info (enter/update/exit)
  74.   public join(
  75.     enter?: (data: T[]) => any,
  76.     update?: (data: UpdateInfo<T>[]) => any,
  77.     exit?: (data: T[]) => any,
  78.   ) {
  79.     if (enter) enter(this.enter);
  80.     if (update) update(this.update);
  81.     if (exit) exit(this.exit);
  82.  
  83.     return this;
  84.   }
  85.  
  86.   // @NOTE: Calculate enter/update/exit arrays by key and diff functions
  87.   private compare(before: Map<string | symbol, T>, after: Map<string | symbol, T>) {
  88.     const enter: T[] = [];
  89.     const update: UpdateInfo<T>[] = [];
  90.     const exit: T[] = [];
  91.  
  92.     for (const datum of after.values()) {
  93.       const key = this.keyFn(datum);
  94.  
  95.       // @NOTE: Added datums
  96.       if (!before.has(key)) {
  97.         enter.push(datum);
  98.       }
  99.     }
  100.  
  101.     for (const datum of before.values()) {
  102.       const key = this.keyFn(datum);
  103.  
  104.       // @NOTE: Deleted datums
  105.       if (!after.has(key)) {
  106.         exit.push(datum);
  107.       }
  108.  
  109.       // @NOTE: Updated datums
  110.       if (after.has(key) && before.has(key)) {
  111.         const newDatum = after.get(key);
  112.         if (this.diffFn(datum, newDatum!)) {
  113.           update.push({
  114.             before: datum,
  115.             after: newDatum!,
  116.           });
  117.         }
  118.       }
  119.     }
  120.  
  121.     return {
  122.       enter,
  123.       update,
  124.       exit,
  125.     };
  126.   }
  127.  
  128.   private getDataByKey(data: T[]) {
  129.     return new Map(data.map(datum => [this.keyFn(datum), datum]));
  130.   }
  131. }
  132.  
  133. // =========================== TEST ===========================
  134.  
  135. type CustomDatum = {
  136.   id: string;
  137.   value: number;
  138. }
  139.  
  140. const arrayComparer = new DataComparer<CustomDatum>()
  141.   .key(datum => datum.id)
  142.   .diff((oldDatum, newDatum) => oldDatum.value !== newDatum.value)
  143. ;
  144.  
  145. const dataBefore: CustomDatum[] = [
  146.   { id: '1', value: 11 },
  147.   { id: '2', value: 22 },
  148.   { id: '3', value: 33 },
  149.   { id: '4', value: 44 },
  150. ];
  151.  
  152. arrayComparer
  153.   .data(dataBefore)
  154.   .join(
  155.     enter => console.log('enter', enter),
  156.     update => console.log('update', update),
  157.     exit => console.log('exit', exit),
  158.   )
  159. ;
  160.  
  161. // ...
  162.  
  163. const dataAfter: CustomDatum[] = [
  164.   { id: '3', value: -33 },
  165.   { id: '4', value: -44 },
  166.   { id: '5', value: 55 },
  167.   { id: '6', value: 66 },
  168. ];
  169.  
  170. arrayComparer
  171.   .data(dataAfter)
  172.   .join(
  173.     enter => console.log('enter', enter),
  174.     update => console.log('update', update),
  175.     exit => console.log('exit', exit),
  176.   )
  177. ;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement