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

okcontract / cells / 10643790258

31 Aug 2024 08:20AM UTC coverage: 92.299% (+0.002%) from 92.297%
10643790258

Pull #38

github

hbbio
array: flattenCellArray supports null
Pull Request #38: 0.3.3: flattenCellArray, Debugger "ee" command, logger

469 of 521 branches covered (90.02%)

Branch coverage included in aggregate %.

59 of 65 new or added lines in 3 files covered. (90.77%)

17 existing lines in 2 files now uncovered.

3558 of 3842 relevant lines covered (92.61%)

967.63 hits per line

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

95.41
/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, NF extends boolean = false>(
1✔
89
  proxy: SheetProxy,
3✔
90
  arr: CellArray<T>,
3✔
91
  compare: (a: T, b: T) => number = defaultComparator,
3✔
92
  noFail?: NF
3✔
93
): MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF> => {
3✔
94
  const coll =
3✔
95
    collector<MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF>>(
3✔
96
      proxy
3✔
97
    );
3✔
98
  return proxy.mapNoPrevious(
3✔
99
    [arr],
3✔
100
    (cells) =>
3✔
101
      coll(
6✔
102
        proxy.mapNoPrevious(
6✔
103
          cells,
6✔
104
          (..._cells) =>
6✔
105
            _cells
8✔
106
              .map((_, index) => index)
8✔
107
              .sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
8✔
108
              .map((index) => cells[index]),
8✔
109
          "_sort",
6✔
110
          noFail || false
6✔
111
        )
6✔
112
      ),
6✔
113
    "sort",
3✔
114
    noFail || false
3✔
115
  );
3✔
116
};
3✔
117

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

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

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

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

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

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

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

1✔
297
/**
1✔
298
 * `flattenCellArray` is a utility function that maps and flattens an array
1✔
299
 * of cells into a single cell that contains a flattened array of values.
1✔
300
 * It creates a new cell that reacts to changes in the input array and updates
1✔
301
 * its value accordingly.
1✔
302
 *
1✔
303
 * @template T - The type of the elements in the input cell array.
1✔
304
 * @template NF - Type indicating if the cells are failsafe.
1✔
305
 *
1✔
306
 * @param {SheetProxy} proxy - An instance of `SheetProxy` used to create new
1✔
307
 * cells and manage dependencies.
1✔
308
 * @param {CellArray<T>} arr - An array of cells, where each cell contains a
1✔
309
 * value of type `T`.
1✔
310
 * @param {string} [name="flatten"] - An optional name for the resulting cell.
1✔
311
 * Defaults to "flatten".
1✔
312
 * @param {NF} [nf] - An optional flag indicating whether the cells can't fail.
1✔
313
 * Defaults to `false`.
1✔
314
 *
1✔
315
 * @returns {MapCell<T[], NF>} - A cell containing a flattened array of values.
1✔
316
 */
1✔
317
export const flattenCellArray = <T, NF extends boolean = false>(
1✔
318
  proxy: SheetProxy,
×
319
  arr: CellArray<T>,
×
320
  name = "flatten",
×
321
  nf?: NF
×
322
) => {
×
323
  const coll = collector<MapCell<T[], NF>>(proxy);
×
324
  return proxy.mapNoPrevious(
×
325
    [arr],
×
NEW
326
    (cells) =>
×
NEW
327
      cells === null
×
NEW
328
        ? null
×
NEW
329
        : coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)),
×
330
    name,
×
331
    nf
×
332
  );
×
333
};
×
334

1✔
335
/**
1✔
336
 * filterPredicateCell updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
337
 * @param arr
1✔
338
 * @param predicate as Cell function that returns `true` for kept values.
1✔
339
 * @returns nothing
1✔
340
 * @description filtered out cells are _not_ deleted
1✔
341
 */
1✔
342
export const filterPredicateCell = <T, NF extends boolean = false>(
1✔
343
  proxy: SheetProxy,
1✔
344
  predicate: AnyCell<(elt: AnyCell<T>) => AnyCell<boolean>>,
1✔
345
  arr: CellArray<T>,
1✔
346
  name = "filter",
1✔
347
  nf?: NF
1✔
348
) => {
1✔
349
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
350
  // Since predicate is a reactive function, we have to instantiate
1✔
351
  // the computation for each cell.
1✔
352
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
1✔
353
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
1✔
354
  const keep = proxy.map(
1✔
355
    [predicate, arr],
1✔
356
    (fn, cells, _prev: AnyCell<boolean>[]) => {
1✔
357
      // console.log({ keep: cells.length });
2✔
358
      // @todo if the predicate function has changed, collect previous mapped cells
2✔
359
      const keep = cells.map((cell) => {
2✔
360
        // We can reuse a cell only if the predicate hasn't changed.
6✔
361
        // @todo collect previously mapped cells for deleted cells in arr
6✔
362
        const reuse =
6✔
363
          prevFn === fn &&
6!
364
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
365
        return reuse || fn(cell);
6✔
366
      });
2✔
367
      prevFn = fn;
2✔
368
      return keep;
2✔
369
    },
2✔
370
    "keep",
1✔
371
    nf
1✔
372
  );
1✔
373
  return proxy.map(
1✔
374
    [arr, keep],
1✔
375
    (cells, _keep) =>
1✔
376
      coll(
2✔
377
        proxy.mapNoPrevious(
2✔
378
          _keep,
2✔
379
          (..._flat) => cells.filter((_, i) => _flat[i]),
2✔
380
          "filter.map"
2✔
381
        )
2✔
382
      ),
2✔
383
    name,
1✔
384
    nf
1✔
385
  );
1✔
386
};
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