Skip to content

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

Open
marcin-kordas-hoc wants to merge 1 commit into
developfrom
feature/HF-68-unique
Open

feat: implement UNIQUE dynamic array function (HF-68)#1708
marcin-kordas-hoc wants to merge 1 commit 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

Medium Risk
New array function affects formula evaluation and spill behavior; empty-result and equality semantics intentionally differ slightly from Excel (#N/A vs #CALC!, config-driven collation).

Overview
Adds Excel-style UNIQUE(array, [by_col], [exactly_once]) as a built-in dynamic array function, returning distinct rows or columns while preserving first-occurrence order.

UniquePlugin implements row/column dedup via ArithmeticHelper.eq, optional exactly_once filtering, first in-range error propagation, and #N/A (EmptyRange) when exactly_once yields no rows—aligned with FILTER’s empty-result behavior rather than Excel’s #CALC!. Parse-time uniqueArraySize uses the input dimensions as an upper bound (same dynamic-size pattern as FILTER).

Wiring includes export from plugin/index.ts, UNIQUE entries in all 17 language packs, changelog, built-in functions table, known-limitations notes, and a shared ADR documenting UNIQUE/SORT design (this PR is UNIQUE-only).

Reviewed by Cursor Bugbot for commit 9eb508d. 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 9eb508d
🔍 Latest deploy log https://app.netlify.com/projects/hyperformula-dev-docs/deploys/6a5b9a475cdd7100085504ef
😎 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 (9eb508d) vs base (fd04f77)

                                     testName |   base |   head | change
------------------------------------------------------------------------
                                      Sheet A | 484.18 | 483.41 | -0.16%
                                      Sheet B | 150.74 | 151.94 | +0.80%
                                      Sheet T | 135.88 |    136 | +0.09%
                                Column ranges | 460.68 | 459.26 | -0.31%
Sheet A:  change value, add/remove row/column |  13.68 |  14.36 | +4.97%
 Sheet B: change value, add/remove row/column | 120.53 |  121.4 | +0.72%
                   Column ranges - add column | 136.94 | 145.53 | +6.27%
                Column ranges - without batch |  420.2 | 423.22 | +0.72%
                        Column ranges - batch | 108.05 | 109.62 | +1.45%

@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>
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.50746% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.88%. Comparing base (fd04f77) to head (9eb508d).

Files with missing lines Patch % Lines
src/interpreter/plugin/UniquePlugin.ts 98.48% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1708      +/-   ##
===========================================
- Coverage    97.17%   96.88%   -0.30%     
===========================================
  Files          176      177       +1     
  Lines        15483    15550      +67     
  Branches      3429     3446      +17     
===========================================
+ Hits         15045    15065      +20     
- Misses         438      485      +47     
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 4 files 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