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

okcontract / cells / 9056733569

13 May 2024 03:20AM UTC coverage: 92.293% (+0.07%) from 92.224%
9056733569

Pull #34

github

xikimay
extend map signature
Pull Request #34: Object flatten

444 of 497 branches covered (89.34%)

Branch coverage included in aggregate %.

95 of 97 new or added lines in 6 files covered. (97.94%)

17 existing lines in 2 files now uncovered.

3400 of 3668 relevant lines covered (92.69%)

1011.71 hits per line

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

95.57
/src/array.ts
1
import { type AnyCell, type 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

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

1✔
56
/**
1✔
57
 * implementation of sort for a cellified array.
1✔
58
 * @description this implementation relies on pointers but reuses the original cells.
1✔
59
 * @param proxy
1✔
60
 * @param arr
1✔
61
 * @param compare comparison function
1✔
62
 */
1✔
63
export const sort = <T>(
1✔
64
  proxy: SheetProxy,
3✔
65
  arr: CellArray<T>,
3✔
66
  compare: (a: T, b: T) => number = (a, b) => (a > b ? 1 : a < b ? -1 : 0)
3✔
67
): CellArray<T> => {
3✔
68
  const coll = collector<CellArray<T>>(proxy);
3✔
69
  return proxy.map(
3✔
70
    [arr],
3✔
71
    (cells) =>
3✔
72
      coll(
6✔
73
        proxy.mapNoPrevious(
6✔
74
          cells,
6✔
75
          (..._cells) =>
6✔
76
            _cells
8✔
77
              .map((_, index) => index)
8✔
78
              .sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
8✔
79
              .map((index) => cells[index]),
8✔
80
          "_sort"
6✔
81
        )
6✔
82
      ),
6✔
83
    "sort"
3✔
84
  );
3✔
85
};
3✔
86

1✔
87
/**
1✔
88
 * mapArrayCell is a variant of `mapArray` with a function taking
1✔
89
 * element cells as arguments.
1✔
90
 * @param proxy
1✔
91
 * @param arr
1✔
92
 * @param fn
1✔
93
 * @returns mapped array cell
1✔
94
 */
1✔
95
export const mapArrayCell = <T, U, NF extends boolean = false>(
1✔
96
  proxy: SheetProxy,
1✔
97
  arr: CellArray<T>,
1✔
98
  fn: AnyCell<(v: AnyCell<T>, idx?: number) => AnyCell<U>>,
1✔
99
  name = "map",
1✔
100
  nf?: NF
1✔
101
): MapCell<MapCell<U, NF>[], NF> => {
1✔
102
  let prevFn: (v: AnyCell<T>) => AnyCell<U>;
1✔
103
  return proxy.map(
1✔
104
    [arr, fn],
1✔
105
    (cells, _fn, prev) => {
1✔
106
      const arr = cells.map((cell, i) =>
2✔
107
        // @todo collect previously mapped cells
6✔
108
        reuseOrCreate(
6✔
109
          _fn === prevFn, // function unchanged
6✔
110
          () => prev?.find((_c) => _c.dependencies?.[0] === cell.id), // reuse
6✔
111
          () => _fn(cell, i) as MapCell<U, NF> // create new map
6✔
112
        )
6✔
113
      );
2✔
114
      prevFn = _fn;
2✔
115
      return arr;
2✔
116
    },
2✔
117
    name,
1✔
118
    nf
1✔
119
  );
1✔
120
};
1✔
121

1✔
122
/**
1✔
123
 * first element of recursive arrays.
1✔
124
 */
1✔
125
export const first = <T>(
1✔
126
  proxy: SheetProxy,
1✔
127
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
128
  name = "last"
1✔
129
): MapCell<T, boolean> => {
1✔
130
  // @todo collectors (on path?)
1✔
131
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
132
    proxy.mapNoPrevious(
2✔
133
      [arr],
2✔
134
      (_arr) => {
2✔
135
        if (!Array.isArray(_arr)) return arr as AnyCell<T>;
2✔
136
        if (_arr.length === 0) return null; // @todo pointer to nullCell?
2✔
137
        return aux(_arr[0]);
1✔
138
      },
2✔
139
      name
2✔
140
    );
2✔
141
  return aux(arr);
1✔
142
};
1✔
143

1✔
144
/**
1✔
145
 * last element of recursive arrays.
1✔
146
 */
1✔
147
export const last = <T>(
1✔
148
  proxy: SheetProxy,
1✔
149
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
150
  name = "last"
1✔
151
): MapCell<T, boolean> => {
1✔
152
  // @todo collectors (on path?)
1✔
153
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
154
    proxy.mapNoPrevious(
2✔
155
      [arr],
2✔
156
      (_arr) => {
2✔
157
        if (Array.isArray(_arr)) return aux(_arr[_arr.length - 1]);
2✔
158
        return arr as AnyCell<T>;
1✔
159
      },
2✔
160
      name
2✔
161
    );
2✔
162
  return aux(arr);
1✔
163
};
1✔
164

1✔
165
/**
1✔
166
 * reduce a cellified array.
1✔
167
 * @param proxy
1✔
168
 * @param arr
1✔
169
 * @param fn
1✔
170
 * @param init
1✔
171
 * @returns reduced cell
1✔
172
 */
1✔
173
export const reduce = <
1✔
174
  T,
2✔
175
  R extends unknown | Promise<unknown>,
2✔
176
  NF extends boolean = false
2✔
177
>(
2✔
178
  proxy: SheetProxy,
2✔
179
  arr: CellArray<T>,
2✔
180
  fn: (acc: R, elt: T, index?: number, length?: number) => R,
2✔
181
  init: R,
2✔
182
  name = "reduce",
2✔
183
  nf?: NF
2✔
184
): MapCell<R, NF> => {
2✔
185
  const coll = collector<MapCell<R, NF>>(proxy);
2✔
186
  return proxy.map(
2✔
187
    [arr],
2✔
188
    (cells) =>
2✔
189
      coll(
5✔
190
        proxy.mapNoPrevious(
5✔
191
          cells,
5✔
192
          (..._cells) =>
5✔
193
            _cells.reduce(
7✔
194
              (acc, elt, i) => fn(acc, elt, i, _cells.length),
7✔
195
              init
7✔
196
            ),
7✔
197
          "_reduce"
5✔
198
        )
5✔
199
      ),
5✔
200
    name,
2✔
201
    nf
2✔
202
  );
2✔
203
};
2✔
204

1✔
205
export const find = <T, NF extends boolean = false>(
1✔
206
  proxy: SheetProxy,
1✔
207
  arr: CellArray<T>,
1✔
208
  predicate: (v: T) => boolean,
1✔
209
  name = "find",
1✔
210
  nf?: NF
1✔
211
) => {
1✔
212
  const coll = collector<MapCell<T, NF>>(proxy);
1✔
213
  return proxy.map(
1✔
214
    [arr],
1✔
215
    (cells) =>
1✔
216
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.find(predicate))),
1✔
217
    name,
1✔
218
    nf
1✔
219
  );
1✔
220
};
1✔
221

1✔
222
export const findIndex = <T, NF extends boolean = false>(
1✔
223
  proxy: SheetProxy,
1✔
224
  arr: CellArray<T>,
1✔
225
  fn: (v: T) => boolean,
1✔
226
  name = "find",
1✔
227
  nf?: NF
1✔
228
) => {
1✔
229
  const coll = collector<MapCell<number, NF>>(proxy);
1✔
230
  return proxy.map(
1✔
231
    [arr],
1✔
232
    (cells) =>
1✔
233
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.findIndex(fn))),
2✔
234
    name,
1✔
235
    nf
1✔
236
  );
1✔
237
};
1✔
238

1✔
239
/**
1✔
240
 * filter updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
241
 * @param arr
1✔
242
 * @param predicate function that returns `true` for kept values.
1✔
243
 * @returns nothing
1✔
244
 * @description filtered out cells are _not_ deleted
1✔
245
 */
1✔
246
export const filter = <T, NF extends boolean = false>(
1✔
247
  proxy: SheetProxy,
1✔
248
  predicate: AnyCell<(elt: T) => boolean>,
1✔
249
  arr: CellArray<T>,
1✔
250
  name = "filter",
1✔
251
  nf?: NF
1✔
252
) => {
1✔
253
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
254
  return proxy.map(
1✔
255
    [predicate, arr],
1✔
256
    (fn, cells) =>
1✔
257
      coll(
2✔
258
        proxy.mapNoPrevious(
2✔
259
          cells,
2✔
260
          (..._cells) => cells.filter((_, i) => fn(_cells[i])),
2✔
261
          "_filter"
2✔
262
        )
2✔
263
      ),
2✔
264
    name,
1✔
265
    nf
1✔
266
  );
1✔
267
};
1✔
268

1✔
269
export const mapFlat = <T, NF extends boolean = false>(
1✔
270
  proxy: SheetProxy,
×
271
  arr: CellArray<T>,
×
272
  name = "flatten",
×
273
  nf?: NF
×
274
) => {
×
275
  const coll = collector<MapCell<T[], NF>>(proxy);
×
NEW
276
  return proxy.map(
×
277
    [arr],
×
278
    (cells) => coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)),
×
279
    name,
×
280
    nf
×
281
  );
×
282
};
×
283

1✔
284
/**
1✔
285
 * filterPredicateCell updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
286
 * @param arr
1✔
287
 * @param predicate as Cell function that returns `true` for kept values.
1✔
288
 * @returns nothing
1✔
289
 * @description filtered out cells are _not_ deleted
1✔
290
 */
1✔
291
export const filterPredicateCell = <T, NF extends boolean = false>(
1✔
292
  proxy: SheetProxy,
1✔
293
  predicate: AnyCell<(elt: AnyCell<T>) => AnyCell<boolean>>,
1✔
294
  arr: CellArray<T>,
1✔
295
  name = "filter",
1✔
296
  nf?: NF
1✔
297
) => {
1✔
298
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
299
  // Since predicate is a reactive function, we have to instantiate
1✔
300
  // the computation for each cell.
1✔
301
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
1✔
302
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
1✔
303
  const keep = proxy.map(
1✔
304
    [predicate, arr],
1✔
305
    (fn, cells, _prev: AnyCell<boolean>[]) => {
1✔
306
      // console.log({ keep: cells.length });
2✔
307
      // @todo if the predicate function has changed, collect previous mapped cells
2✔
308
      const keep = cells.map((cell) => {
2✔
309
        // We can reuse a cell only if the predicate hasn't changed.
6✔
310
        // @todo collect previously mapped cells for deleted cells in arr
6✔
311
        const reuse =
6✔
312
          prevFn === fn &&
6!
313
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
314
        return reuse || fn(cell);
6✔
315
      });
2✔
316
      prevFn = fn;
2✔
317
      return keep;
2✔
318
    },
2✔
319
    "keep",
1✔
320
    nf
1✔
321
  );
1✔
322
  return proxy.map(
1✔
323
    [arr, keep],
1✔
324
    (cells, _keep) =>
1✔
325
      coll(
2✔
326
        proxy.mapNoPrevious(
2✔
327
          _keep,
2✔
328
          (..._flat) => cells.filter((_, i) => _flat[i]),
2✔
329
          "filter.map"
2✔
330
        )
2✔
331
      ),
2✔
332
    name,
1✔
333
    nf
1✔
334
  );
1✔
335
};
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