# core/aggregators/representation.grid.aggregator.ts

> 55 lines of code and 19 definitions.

Tree: GovLab Patterns
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/patterns#file-patterns-core-aggregators-representation-grid-aggregator-ts
Source text: https://banes-lab.com/source/patterns/core/aggregators/representation.grid.aggregator.ts.txt

Listed in [core/aggregators](https://banes-lab.com/api/source/patterns/core/aggregators.md), after [core/aggregators/representation.graph.aggregator.ts](https://banes-lab.com/source/patterns/core/aggregators/representation.graph.aggregator.ts.md) and before [core/aggregators/representation.sequence.aggregator.ts](https://banes-lab.com/source/patterns/core/aggregators/representation.sequence.aggregator.ts.md).

## Definitions

- `isNumberPair` (lexical_declaration, line 11)
- `observe` (method_definition, line 55, exported)
- `update` (method_definition, line 28, exported)
- `PAIR` (lexical_declaration, line 7)
- `DEFAULT_CELL` (lexical_declaration, line 8)
- `CELL_SEP` (lexical_declaration, line 9)
- `GridAccumulator` (class_declaration, line 17, exported)
- `field` (public_field_definition, line 18, exported)
- `cellSize` (public_field_definition, line 19, exported)
- `cells` (public_field_definition, line 20, exported)
- `points` (public_field_definition, line 21, exported)
- `constructor` (method_definition, line 23, exported)
- `sample` (method_definition, line 34, exported)
- `key` (lexical_declaration, line 35, exported)
- `[cellX = 0, cellY = 0]` (lexical_declaration, line 39, exported)
- `result` (method_definition, line 43, exported)
- `densestCell` (lexical_declaration, line 44, exported)
- `densestCount` (lexical_declaration, line 45, exported)
- `[x, y]` (lexical_declaration, line 60, exported)

## Contained in

- [core/aggregators](https://banes-lab.com/anatomy/patterns/folder-patterns-core-aggregators.md)

## Uses

- [core/counters/base.counter.ts](https://banes-lab.com/source/patterns/core/counters/base.counter.ts.md)
- [core/selectors/record.selector.ts](https://banes-lab.com/source/patterns/core/selectors/record.selector.ts.md)

## Linked from

- [core/aggregators](https://banes-lab.com/anatomy/patterns/folder-patterns-core-aggregators.md)
- [core/counters](https://banes-lab.com/anatomy/patterns/folder-patterns-core-counters.md)
- [core/selectors](https://banes-lab.com/anatomy/patterns/folder-patterns-core-selectors.md)

## Source

```typescript
import type { GridSummary } from "#types/representation.types";
import type { Rng } from "#types/seed.types";
import { foldRecords } from "#core/selectors/record.selector";
import { increment } from "#core/counters/base.counter";
import { weightedChoice } from "#core/selectors/counter.selector";

const PAIR = 2;
const DEFAULT_CELL = 1;
const CELL_SEP = ",";

const isNumberPair = function isNumberPair(value: unknown): value is [number, number] {
    return (
        Array.isArray(value) && value.length === PAIR && typeof value[0] === "number" && typeof value[1] === "number"
    );
};

export class GridAccumulator {
    private readonly field: string;
    private readonly cellSize: number;
    private readonly cells = new Map<string, number>();
    private points = 0;

    public constructor(field: string, cellSize = DEFAULT_CELL) {
        this.field = field;
        this.cellSize = cellSize;
    }

    public update(chunk: readonly unknown[]): void {
        foldRecords(chunk, this.field, (value) => {
            this.observe(value);
        });
    }

    public sample(rng: Rng): [number, number] | null {
        const key = weightedChoice(rng, this.cells);
        if (key === null) {
            return null;
        }
        const [cellX = 0, cellY = 0] = key.split(CELL_SEP).map(Number);
        return [(cellX + rng.next()) * this.cellSize, (cellY + rng.next()) * this.cellSize];
    }

    public result(): GridSummary {
        let densestCell = "";
        let densestCount = this.cells.size === 0 ? 0 : -1;
        for (const [cell, count] of this.cells) {
            if (count > densestCount) {
                densestCell = cell;
                densestCount = count;
            }
        }
        return { densestCell, densestCount, distinctCells: this.cells.size, field: this.field, points: this.points };
    }

    private observe(value: unknown): void {
        if (!isNumberPair(value)) {
            return;
        }
        this.points += 1;
        const [x, y] = value;
        increment(this.cells, `${Math.floor(x / this.cellSize)}${CELL_SEP}${Math.floor(y / this.cellSize)}`);
    }
}
```
