Skip to content

feat: implement UNIQUE dynamic array function (HF-68)#1708

Open
marcin-kordas-hoc wants to merge 4 commits into
developfrom
feature/HF-68-unique
Open

feat: implement UNIQUE dynamic array function (HF-68)#1708
marcin-kordas-hoc wants to merge 4 commits into
developfrom
feature/HF-68-unique

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

What & why

Implements UNIQUE (HF-68, child of HF-28 "Modern dynamic array functions", sibling of the shipped SEQUENCE and of VSTACK/HSTACK). Adds UNIQUE(array, [by_col], [exactly_once]) as a dynamic array function.

Tests: handsontable/hyperformula-tests#25 (paired).
Sibling PR: #1707 (SORT / HF-69).
ADR: docs/adr/2026-07-13-sort-unique-array-functions.md.

Behavior

  • Returns the distinct rows (or columns when by_col is TRUE) of the input, preserving first-occurrence order.
  • by_col: FALSE (default) compares rows; TRUE compares columns.
  • exactly_once: TRUE returns only rows/columns occurring exactly once; FALSE (default) returns all distinct.
  • Equality reuses ArithmeticHelper.eqcase-insensitive by default (honors caseSensitive), matching Excel's UNIQUE.
  • Result size is data-dependent; mirrors FILTER (predict input size as upper bound, return the smaller actual result).

Design

Mirrors the FILTER machinery for dynamic-size results: sizeOfResultArrayMethod + vectorizationForbidden: true, runtime via runFunction, parse-time size method returning a fresh ArraySize (drops the input's isRef flag). Deduplication is O(n²) in the number of vectors because locale-aware equality is not trivially hashable — noted in code; acceptable for v1.

Notes — divergences from Excel (surfaced here + inline + in tests)

  • Empty result (only via exactly_once when nothing occurs exactly once) → #N/A. Excel returns #CALC!, which HyperFormula has no type for; mirrors FILTER's empty-result mapping (ADR dec_8).
  • Comparison honors HF's collation config rather than a byte-for-byte Excel oracle (no live Excel in this environment; ADR con_1).
  • In-range errors propagate (first error found; ADR dec_7).

Definition of Done

  • Production code (UniquePlugin.ts, registered via plugin/index.ts)
  • i18n — all 17 language packs (authoritative MS Functions Translator names; enUS inherits enGB)
  • Tests (paired tests PR) — across the standard array-function groups, dual-env safe
  • Docs — built-in-functions.md, known-limitations.md
  • JSDoc on all methods
  • CHANGELOG entry
  • ADR with audit-verified citations

Source: https://app.clickup.com/t/86c89q1tq


Note

Low Risk
Isolated new function plugin with established dynamic-array patterns; no changes to auth, persistence, or core calculation paths beyond registering UNIQUE.

Overview
This PR implements UNIQUE(array, [by_col], [exactly_once]) as a dynamic array function (HF-68), following the same pattern as FILTER and SEQUENCE.

A new UniquePlugin deduplicates rows (or columns when by_col is TRUE) using ArithmeticHelper.eq, keeps first-occurrence order, and supports exactly_once. Parse-time sizing uses the input dimensions as an upper bound; runtime may return a smaller spilled array. The first CellError in the input is propagated; an empty result under exactly_once returns #N/A (EmptyRange) instead of Excel’s #CALC!.

UniquePlugin is exported from plugin/index.ts. UNIQUE is added to all 17 language packs. CHANGELOG, built-in-functions.md, known-limitations.md, and a shared SORT/UNIQUE ADR document behavior and Excel divergences.

Reviewed by Cursor Bugbot for commit 5e96194. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jul 13, 2026

Copy link
Copy Markdown

Deploy Preview for hyperformula-dev-docs ready!

Name Link
🔨 Latest commit 5e96194
🔍 Latest deploy log https://app.netlify.com/projects/hyperformula-dev-docs/deploys/6a605f40145d3f0008342190
😎 Deploy Preview https://deploy-preview-1708--hyperformula-dev-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qunabu

qunabu commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Performance comparison of head (5e96194) vs base (fd04f77)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  387.72 |  384.88 | -0.73%
                                      Sheet B |  126.29 |  123.94 | -1.86%
                                      Sheet T |  110.03 |  108.54 | -1.35%
                                Column ranges |  411.76 |  403.81 | -1.93%
                                Sorted lookup | 11778.7 | 11912.2 | +1.13%
Sheet A:  change value, add/remove row/column |   12.85 |   12.39 | -3.58%
 Sheet B: change value, add/remove row/column |  112.46 |  110.32 | -1.90%
                   Column ranges - add column |  128.01 |  125.42 | -2.02%
                Column ranges - without batch |  395.26 |  389.32 | -1.50%
                        Column ranges - batch |  101.75 |   97.34 | -4.33%

@marcin-kordas-hoc
marcin-kordas-hoc requested a review from sequba July 16, 2026 12:20
Add UNIQUE(array, [by_col], [exactly_once]) as a dynamic array function,
mirroring the FILTER machinery for data-dependent result size. Deduplication
preserves first-occurrence order and reuses ArithmeticHelper for equality
(case-insensitive by default, honoring the caseSensitive config). Supports
by_col (unique columns) and exactly_once (values occurring exactly once).
Errors in the input range propagate; an empty exactly_once result yields #N/A.

Includes i18n for all 17 language packs, docs (built-in-functions,
known-limitations), a changelog entry, and the shared ADR.

Source: https://app.clickup.com/t/86c89q1tq
ADR: docs/adr/2026-07-13-sort-unique-array-functions.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
marcin-kordas-hoc and others added 3 commits July 21, 2026 10:30
Without enableArrayArithmeticForArguments, an array expression passed as
the first argument (e.g. =UNIQUE(A1:A3*2)) is coerced as a scalar and only
the first cell is computed. Matches FILTER/VSTACK/HSTACK/SORT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s (HF-68)

The compared vectors are always rows (or columns, after transpose) of the
same rectangular range, so they never differ in length — the guard was dead
code. Removes the last uncovered line in UniquePlugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.19%. Comparing base (fd04f77) to head (5e96194).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1708      +/-   ##
===========================================
+ Coverage    97.17%   97.19%   +0.02%     
===========================================
  Files          176      177       +1     
  Lines        15483    15548      +65     
  Branches      3429     3445      +16     
===========================================
+ Hits         15045    15112      +67     
+ Misses         438      436       -2     
Files with missing lines Coverage Δ
src/i18n/languages/csCZ.ts 100.00% <ø> (ø)
src/i18n/languages/daDK.ts 100.00% <ø> (ø)
src/i18n/languages/deDE.ts 100.00% <ø> (ø)
src/i18n/languages/enGB.ts 100.00% <ø> (ø)
src/i18n/languages/esES.ts 100.00% <ø> (ø)
src/i18n/languages/fiFI.ts 100.00% <ø> (ø)
src/i18n/languages/frFR.ts 100.00% <ø> (ø)
src/i18n/languages/huHU.ts 100.00% <ø> (ø)
src/i18n/languages/idID.ts 100.00% <ø> (ø)
src/i18n/languages/itIT.ts 100.00% <ø> (ø)
... and 9 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants