• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

okcontract / cells / 10088481341

25 Jul 2024 05:03AM UTC coverage: 92.184% (-0.1%) from 92.324%
10088481341

Pull #37

github

hbbio
array: defaultComparator, noFail option for sort
Pull Request #37: Proxy names, improved debugging

457 of 510 branches covered (89.61%)

Branch coverage included in aggregate %.

61 of 70 new or added lines in 6 files covered. (87.14%)

41 existing lines in 2 files now uncovered.

3506 of 3789 relevant lines covered (92.53%)

982.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.85
/src/array.ts
1
import type { AnyCell, MapCell, ValueCell } from "./cell";
1✔
2
import { collector, reuseOrCreate } from "./gc";
1✔
3
import type { SheetProxy } from "./proxy";
1✔
4

1✔
5
export type CellArray<T> = AnyCell<AnyCell<T>[]>;
1✔
6
export type ValueCellArray<T> = ValueCell<ValueCell<T>[]>;
1✔
7

1✔
8
/**
1✔
9
 * mapArray implements .map() for a cellified array.
1✔
10
 *
1✔
11
 * @param proxy
1✔
12
 * @param arr canonical form cell array
1✔
13
 * @param fn to map each element cell
1✔
14
 * @returns mapped array cell
1✔
15
 *
1✔
16
 * @description This function reuses existing mapped cells.
1✔
17
 * @todo Delete unused mapped cells
1✔
18
 */
1✔
19
export const mapArray = <T, U>(
1✔
20
  proxy: SheetProxy,
2✔
21
  arr: CellArray<T>,
2✔
22
  fn: (
2✔
23
    v: T,
2✔
24
    index?: number,
2✔
25
    cell?: AnyCell<T>
2✔
26
  ) => U | Promise<U | AnyCell<U>> | AnyCell<U>,
2✔
27
  name = "map"
2✔
28
): MapCell<MapCell<U, false>[], false> =>
2✔
29
  proxy.map(
2✔
30
    [arr],
2✔
31
    (cells, prev) => {
2✔
32
      if (!Array.isArray(cells))
6✔
33
        throw new Error(`not an array: ${typeof cells}`);
6!
34
      if (!cells) return [];
6!
35
      const set = new Set((prev || []).map((cell) => cell.id));
6✔
36
      const res = cells.map((cell, index) => {
6✔
37
        // reuse previously mapped cell
20✔
38
        const reuse = prev?.find((_c) => _c.dependencies?.[0] === cell.id);
20✔
39
        if (reuse !== undefined) set.delete(reuse.id);
20✔
40
        return (
20✔
41
          reuse ||
20✔
42
          // create new map
8✔
43
          proxy.map(
8✔
44
            [cell],
8✔
45
            (_cell) => fn(_cell, index, cell),
8✔
46
            `${cell.id}:[${index}]`
8✔
47
          )
8✔
48
        );
20✔
49
      });
6✔
50
      // collect unused previously mapped cells
6✔
51
      proxy._sheet.collect(...[...set]);
6✔
52
      return res;
6✔
53
    },
6✔
54
    name
2✔
55
  );
2✔
56

1✔
57
/**
1✔
58
 * Compares two values and returns a number indicating their order.
1✔
59
 *
1✔
60
 * This function is designed to be used as a comparator in sorting functions.
1✔
61
 * It returns:
1✔
62
 * - `1` if `a` is greater than `b`
1✔
63
 * - `-1` if `a` is less than `b`
1✔
64
 * - `0` if `a` is equal to `b`
1✔
65
 *
1✔
66
 * @param {*} a - The first value to compare.
1✔
67
 * @param {*} b - The second value to compare.
1✔
68
 * @returns {number} - Order.
1✔
69
 *
1✔
70
 * @example
1✔
71
 * const array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
1✔
72
 * array.sort(defaultComparator);
1✔
73
 */
1✔
74
export const defaultComparator = <T>(a: T, b: T): number =>
1✔
75
  a > b ? 1 : a < b ? -1 : 0;
38✔
76

1✔
77
/**
1✔
78
 * implementation of sort for a cellified array.
1✔
79
 * @description this implementation relies on pointers but reuses the original cells.
1✔
80
 * @param proxy
1✔
81
 * @param arr
1✔
82
 * @param compare comparison function
1✔
83
 * @param noFail should be true when the following conditions are met:
1✔
84
 * 1. compare must be defined
1✔
85
 * 2. cells must be an array (possibly empty)
1✔
86
 * 3. compare should never fail
1✔
87
 */
1✔
88
export const sort = <T>(
1✔
89
  proxy: SheetProxy,
3✔
90
  arr: CellArray<T>,
3✔
91
  compare: (a: T, b: T) => number = defaultComparator,
3✔
92
  noFail = false
3✔
93
): CellArray<T> => {
3✔
94
  const coll = collector<CellArray<T>>(proxy);
3✔
95
  return proxy.mapNoPrevious(
3✔
96
    [arr],
3✔
97
    (cells) =>
3✔
98
      coll(
6✔
99
        proxy.mapNoPrevious(
6✔
100
          cells,
6✔
101
          (..._cells) =>
6✔
102
            _cells
8✔
103
              .map((_, index) => index)
8✔
104
              .sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
8✔
105
              .map((index) => cells[index]),
8✔
106
          "_sort",
6✔
107
          noFail
6✔
108
        )
6✔
109
      ),
6✔
110
    "sort",
3✔
111
    noFail
3✔
112
  );
3✔
113
};
3✔
114

1✔
115
/**
1✔
116
 * mapArrayCell is a variant of `mapArray` with a function taking
1✔
117
 * element cells as arguments.
1✔
118
 * @param proxy
1✔
119
 * @param arr
1✔
120
 * @param fn
1✔
121
 * @returns mapped array cell
1✔
122
 */
1✔
123
export const mapArrayCell = <T, U, NF extends boolean = false>(
1✔
124
  proxy: SheetProxy,
1✔
125
  arr: CellArray<T>,
1✔
126
  fn: AnyCell<(v: AnyCell<T>, idx?: number) => AnyCell<U>>,
1✔
127
  name = "map",
1✔
128
  nf?: NF
1✔
129
): MapCell<MapCell<U, NF>[], NF> => {
1✔
130
  let prevFn: (v: AnyCell<T>) => AnyCell<U>;
1✔
131
  return proxy.map(
1✔
132
    [arr, fn],
1✔
133
    (cells, _fn, prev) => {
1✔
134
      const arr = cells.map((cell, i) =>
2✔
135
        // @todo collect previously mapped cells
6✔
136
        reuseOrCreate(
6✔
137
          _fn === prevFn, // function unchanged
6✔
138
          () => prev?.find((_c) => _c.dependencies?.[0] === cell.id), // reuse
6✔
139
          () => _fn(cell, i) as MapCell<U, NF> // create new map
6✔
140
        )
6✔
141
      );
2✔
142
      prevFn = _fn;
2✔
143
      return arr;
2✔
144
    },
2✔
145
    name,
1✔
146
    nf
1✔
147
  );
1✔
148
};
1✔
149

1✔
150
/**
1✔
151
 * first element of recursive arrays.
1✔
152
 */
1✔
153
export const first = <T>(
1✔
154
  proxy: SheetProxy,
1✔
155
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
156
  name = "last"
1✔
157
): MapCell<T, boolean> => {
1✔
158
  // @todo collectors (on path?)
1✔
159
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
160
    proxy.mapNoPrevious(
2✔
161
      [arr],
2✔
162
      (_arr) => {
2✔
163
        if (!Array.isArray(_arr)) return arr as AnyCell<T>;
2✔
164
        if (_arr.length === 0) return null; // @todo pointer to nullCell?
2✔
165
        return aux(_arr[0]);
1✔
166
      },
2✔
167
      name
2✔
168
    );
2✔
169
  return aux(arr);
1✔
170
};
1✔
171

1✔
172
/**
1✔
173
 * last element of recursive arrays.
1✔
174
 */
1✔
175
export const last = <T>(
1✔
176
  proxy: SheetProxy,
1✔
177
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
178
  name = "last"
1✔
179
): MapCell<T, boolean> => {
1✔
180
  // @todo collectors (on path?)
1✔
181
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
182
    proxy.mapNoPrevious(
2✔
183
      [arr],
2✔
184
      (_arr) => {
2✔
185
        if (Array.isArray(_arr)) return aux(_arr[_arr.length - 1]);
2✔
186
        return arr as AnyCell<T>;
1✔
187
      },
2✔
188
      name
2✔
189
    );
2✔
190
  return aux(arr);
1✔
191
};
1✔
192

1✔
193
/**
1✔
194
 * reduce a cellified array.
1✔
195
 * @param proxy
1✔
196
 * @param arr
1✔
197
 * @param fn
1✔
198
 * @param init
1✔
199
 * @returns reduced cell
1✔
200
 */
1✔
201
export const reduce = <
1✔
202
  T,
2✔
203
  R extends unknown | Promise<unknown>,
2✔
204
  NF extends boolean = false
2✔
205
>(
2✔
206
  proxy: SheetProxy,
2✔
207
  arr: CellArray<T>,
2✔
208
  fn: (acc: R, elt: T, index?: number, cell?: AnyCell<T>) => R,
2✔
209
  init: R,
2✔
210
  name = "reduce",
2✔
211
  nf?: NF
2✔
212
): MapCell<R, NF> => {
2✔
213
  const coll = collector<MapCell<R, NF>>(proxy);
2✔
214
  return proxy.map(
2✔
215
    [arr],
2✔
216
    (cells) =>
2✔
217
      coll(
5✔
218
        proxy.mapNoPrevious(
5✔
219
          cells,
5✔
220
          (..._cells) =>
5✔
221
            _cells.reduce((acc, elt, i) => fn(acc, elt, i, cells[i]), init),
7✔
222
          "_reduce"
5✔
223
        )
5✔
224
      ),
5✔
225
    name,
2✔
226
    nf
2✔
227
  );
2✔
228
};
2✔
229

1✔
230
export const find = <T, NF extends boolean = false>(
1✔
231
  proxy: SheetProxy,
1✔
232
  arr: CellArray<T>,
1✔
233
  predicate: (v: T) => boolean,
1✔
234
  name = "find",
1✔
235
  nf?: NF
1✔
236
) => {
1✔
237
  const coll = collector<MapCell<T, NF>>(proxy);
1✔
238
  return proxy.map(
1✔
239
    [arr],
1✔
240
    (cells) =>
1✔
241
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.find(predicate))),
1✔
242
    name,
1✔
243
    nf
1✔
244
  );
1✔
245
};
1✔
246

1✔
247
export const findIndex = <T, NF extends boolean = false>(
1✔
248
  proxy: SheetProxy,
1✔
249
  arr: CellArray<T>,
1✔
250
  fn: (v: T) => boolean,
1✔
251
  name = "find",
1✔
252
  nf?: NF
1✔
253
) => {
1✔
254
  const coll = collector<MapCell<number, NF>>(proxy);
1✔
255
  return proxy.map(
1✔
256
    [arr],
1✔
257
    (cells) =>
1✔
258
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.findIndex(fn))),
2✔
259
    name,
1✔
260
    nf
1✔
261
  );
1✔
262
};
1✔
263

1✔
264
/**
1✔
265
 * filter updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
266
 * @param arr
1✔
267
 * @param predicate function that returns `true` for kept values.
1✔
268
 * @returns nothing
1✔
269
 * @description filtered out cells are _not_ deleted
1✔
270
 */
1✔
271
export const filter = <T, NF extends boolean = false>(
1✔
272
  proxy: SheetProxy,
1✔
273
  predicate: AnyCell<(elt: T) => boolean>,
1✔
274
  arr: CellArray<T>,
1✔
275
  name = "filter",
1✔
276
  nf?: NF
1✔
277
) => {
1✔
278
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
279
  return proxy.map(
1✔
280
    [predicate, arr],
1✔
281
    (fn, cells) =>
1✔
282
      coll(
2✔
283
        proxy.mapNoPrevious(
2✔
284
          cells,
2✔
285
          (..._cells) => cells.filter((_, i) => fn(_cells[i])),
2✔
286
          "_filter"
2✔
287
        )
2✔
288
      ),
2✔
289
    name,
1✔
290
    nf
1✔
291
  );
1✔
292
};
1✔
293

1✔
294
export const mapFlat = <T, NF extends boolean = false>(
1✔
UNCOV
295
  proxy: SheetProxy,
×
UNCOV
296
  arr: CellArray<T>,
×
UNCOV
297
  name = "flatten",
×
298
  nf?: NF
×
299
) => {
×
300
  const coll = collector<MapCell<T[], NF>>(proxy);
×
301
  return proxy.mapNoPrevious(
×
302
    [arr],
×
303
    (cells) => coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)),
×
304
    name,
×
305
    nf
×
306
  );
×
307
};
×
308

1✔
309
/**
1✔
310
 * filterPredicateCell updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
311
 * @param arr
1✔
312
 * @param predicate as Cell function that returns `true` for kept values.
1✔
313
 * @returns nothing
1✔
314
 * @description filtered out cells are _not_ deleted
1✔
315
 */
1✔
316
export const filterPredicateCell = <T, NF extends boolean = false>(
1✔
317
  proxy: SheetProxy,
1✔
318
  predicate: AnyCell<(elt: AnyCell<T>) => AnyCell<boolean>>,
1✔
319
  arr: CellArray<T>,
1✔
320
  name = "filter",
1✔
321
  nf?: NF
1✔
322
) => {
1✔
323
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
324
  // Since predicate is a reactive function, we have to instantiate
1✔
325
  // the computation for each cell.
1✔
326
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
1✔
327
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
1✔
328
  const keep = proxy.map(
1✔
329
    [predicate, arr],
1✔
330
    (fn, cells, _prev: AnyCell<boolean>[]) => {
1✔
331
      // console.log({ keep: cells.length });
2✔
332
      // @todo if the predicate function has changed, collect previous mapped cells
2✔
333
      const keep = cells.map((cell) => {
2✔
334
        // We can reuse a cell only if the predicate hasn't changed.
6✔
335
        // @todo collect previously mapped cells for deleted cells in arr
6✔
336
        const reuse =
6✔
337
          prevFn === fn &&
6!
UNCOV
338
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
339
        return reuse || fn(cell);
6✔
340
      });
2✔
341
      prevFn = fn;
2✔
342
      return keep;
2✔
343
    },
2✔
344
    "keep",
1✔
345
    nf
1✔
346
  );
1✔
347
  return proxy.map(
1✔
348
    [arr, keep],
1✔
349
    (cells, _keep) =>
1✔
350
      coll(
2✔
351
        proxy.mapNoPrevious(
2✔
352
          _keep,
2✔
353
          (..._flat) => cells.filter((_, i) => _flat[i]),
2✔
354
          "filter.map"
2✔
355
        )
2✔
356
      ),
2✔
357
    name,
1✔
358
    nf
1✔
359
  );
1✔
360
};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc