SubtractionIdentity
The prefix-sum subtraction identity in one picture:
sum(values[lo..hi]) = prefix[hi+1] − prefix[lo]
A values: number[] row sits above a derived prefix row of length values.length + 1. The window bracket spans the queried range; the two prefix cells participating in the subtraction (prefix[lo] and prefix[hi+1]) pulse with an accent border; the result strip renders p[hi+1] − p[lo] = answer. Drives range-sum query games, Kadane-style segment lessons, the prefix-XOR pattern, and any teaching surface where the two endpoints of the subtraction are the load-bearing visual.
Pure UI primitive — the caller passes values, lo, hi. The component clamps lo and hi to safe bounds, derives prefix[] once per render, and never mutates input. Choose op="sum" for range sums or op="xor" for range XOR — the identity has the same shape.
Installation
npx shadcn@latest add https://craftbits.dev/r/subtraction-identity.jsonUsage
import { SubtractionIdentity } from "@craft-bits/core";
<SubtractionIdentity values={[3, 1, 4, 1, 5, 9, 2, 6]} lo={1} hi={4} />Animate the range by driving lo / hi from a parent reducer or timer to step through a discovery sequence:
const [lo, setLo] = useState(0);
const [hi, setHi] = useState(2);
<SubtractionIdentity values={values} lo={lo} hi={hi} />Range XOR query — the same identity, swap the operator:
<SubtractionIdentity
values={[5, 3, 6, 1, 2]}
lo={1}
hi={3}
op="xor"
/>Hide the result strip while the parent is gating the reveal behind a prediction interaction:
<SubtractionIdentity
values={values}
lo={lo}
hi={hi}
showResult={false}
/>Custom row labels for a vocabulary-specific lesson:
<SubtractionIdentity
values={values}
lo={lo}
hi={hi}
valuesLabel="a"
prefixLabel="P"
tone="success"
/>Understanding the component
- The identity, made visible. The component renders the two rows in vertical alignment so each
prefix[i]sits directly under the gap to the left ofvalues[i]. That alignment is the picture of the identity: the cumulative sum up to but not including indexilives atprefix[i]. The window bracket under the values row marks the range; the two pulsing prefix cells mark the subtraction. prefix[]is derived, never owned. Every render recomputesprefixfrom(op, values)viacombine(prefix[i], values[i]). There is no internal state to drift, and the caller can swap the array in a single prop change.loandhiare clamped, not validated. Out-of-range or inverted ranges collapse to the nearest valid window instead of crashing — the component is safe to drive from a controlled parent that may briefly hold transient values mid-animation.- Endpoints are
loandhi+1. The two pulsing prefix cells areprefix[lo](left) andprefix[hi+1](right). The off-by-one is the whole point of the identity; the labels (p[i]) make it visible. opselects the reducer."sum"uses ordinary addition and renders the equation with a minus sign;"xor"uses bitwise XOR and renders the equation with the XOR glyph. Both share the identity element0, soprefix[0] = 0in both modes. Add new ops by extending the internalOPStable.- Window bracket and result strip are derived too. The bracket width and offset come straight from
(safeLo, safeHi); the result strip text comes straight fromprefix[hi+1],prefix[lo], and the inverse operation. Disable the result strip withshowResult={false}when a parent wants to gate the reveal. - Four tones.
accent(default),success,warning,danger. The tone paints the in-range value cells, both endpoint prefix cells, and the result strip — never the resting cells. - Reduced motion. The endpoint pulse, the window-bracket animation, and the result-strip transition all collapse to instant under
prefers-reduced-motion: reduce. The picture still updates; only the motion drops.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
values | readonly number[] | required | Input array. |
lo | number | required | Inclusive lower bound. Clamped to [0, values.length - 1]. |
hi | number | required | Inclusive upper bound. Clamped to [lo, values.length - 1]. |
op | "sum" | "xor" | "sum" | Reducer family — picks the (combine, inverse) pair. |
tone | "accent" | "success" | "warning" | "danger" | "accent" | Highlight palette for endpoints and the result strip. |
valuesLabel | string | "values" | Row label above the values row. |
prefixLabel | string | "prefix" | Row label above the prefix row. |
showResult | boolean | true | Toggle the result strip. |
transition | Transition | SPRINGS.smooth | Override cell transitions. Reduced-motion users snap regardless. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root is
role="group"with anaria-labelledbypointing at a visually-hidden summary like Subtraction identity over 8 values: sum of values 1 through 4 equals prefix 5 minus prefix 1, which is 11 — screen readers hear the full identity at a glance and re-narrate on every range change viaaria-live="polite". - Every value cell exposes
role="img"and anaria-labelnaming its index, value, and whether it's in range. Every prefix cell exposesrole="img"and anaria-labelnaming its index, value, and whether it's the left or right endpoint of the subtraction. - The result strip is
role="status"with anaria-labelspelling the equation in words (prefix 5 minus prefix 1 equals 11). - Cell hit targets are at least 44 by 44 px (WCAG 2.5.8 AAA) via
min-h-[2.75rem] min-w-[2.75rem]. - The root exposes
data-tone,data-op, anddata-in-range; every cell exposesdata-state(in-range/out, orendpoint/rest) and prefix endpoints exposedata-endpoint(left/right/none) so consumer apps can hook custom styles or assistive tooling. - Tone is never the only signal — endpoint cells gain a thicker inset ring and a stronger fill, the in-range value cells gain a tinted background, and the window bracket gains a visible underline, so colourblind users see the highlight even when the tone hue is hard to discriminate.
- Motion respects
prefers-reduced-motion: reduce— the endpoint pulse, the window bracket, and the result strip all collapse to instant. The state still updates; only the motion drops.
Credits
- Extracted from:
AlgoFlashcards(src/lessons/primitives/construction/SubtractionIdentity.tsx). The source was a five-phase lesson teaching the general subtraction-identity pattern via the "exactly K distinct" problem — a trap reducer (try direct sliding window, hit the non-monotonic wall), reveal phase (step throughatMost(K)andatMost(K-1)side by side), prediction-gated subtraction, MagicMove code morph, and micro-queries — wired to bespoke chrome (LessonButton,PredictionGate,MagicMoveBlock,playSound, semantic-hex tokens). The library extract drops the algorithm-specific lesson — phase reducer, prediction gates, code morph, audio cues — and exposes the canonical incarnation of the same identity: prefix sums. The same shape (a forward accumulator with an inverse on two endpoints) covers range sum, range XOR, and anyf(values[lo..hi]) = g(prefix[hi+1], prefix[lo])query — switchable via theopprop.