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.

Subtraction identity over 8 values: sum of values 1 through 4 equals prefix 5 minus prefix 1, which is 11.
p[5]p[1]11
[1, 4]
Customize
Reducer
0
Highlight
0

Installation

npx shadcn@latest add https://craftbits.dev/r/subtraction-identity.json

Usage

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

  1. 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 of values[i]. That alignment is the picture of the identity: the cumulative sum up to but not including index i lives at prefix[i]. The window bracket under the values row marks the range; the two pulsing prefix cells mark the subtraction.
  2. prefix[] is derived, never owned. Every render recomputes prefix from (op, values) via combine(prefix[i], values[i]). There is no internal state to drift, and the caller can swap the array in a single prop change.
  3. lo and hi are 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.
  4. Endpoints are lo and hi+1. The two pulsing prefix cells are prefix[lo] (left) and prefix[hi+1] (right). The off-by-one is the whole point of the identity; the labels (p[i]) make it visible.
  5. op selects 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 element 0, so prefix[0] = 0 in both modes. Add new ops by extending the internal OPS table.
  6. Window bracket and result strip are derived too. The bracket width and offset come straight from (safeLo, safeHi); the result strip text comes straight from prefix[hi+1], prefix[lo], and the inverse operation. Disable the result strip with showResult={false} when a parent wants to gate the reveal.
  7. 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.
  8. 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

PropTypeDefaultDescription
valuesreadonly number[]requiredInput array.
lonumberrequiredInclusive lower bound. Clamped to [0, values.length - 1].
hinumberrequiredInclusive 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.
valuesLabelstring"values"Row label above the values row.
prefixLabelstring"prefix"Row label above the prefix row.
showResultbooleantrueToggle the result strip.
transitionTransitionSPRINGS.smoothOverride cell transitions. Reduced-motion users snap regardless.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="group" with an aria-labelledby pointing 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 via aria-live="polite".
  • Every value cell exposes role="img" and an aria-label naming its index, value, and whether it's in range. Every prefix cell exposes role="img" and an aria-label naming its index, value, and whether it's the left or right endpoint of the subtraction.
  • The result strip is role="status" with an aria-label spelling 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, and data-in-range; every cell exposes data-state (in-range / out, or endpoint / rest) and prefix endpoints expose data-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 through atMost(K) and atMost(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 any f(values[lo..hi]) = g(prefix[hi+1], prefix[lo]) query — switchable via the op prop.