Reading Order IILoading saved progress…

Reading Order II

Implement readingOrderII(elements) — take a list of positioned boxes scattered on a 2D canvas and return them in the order a person would read them: top rows first, left to right within each row. This is the follow-up to Reading Order, where every box sat on an exact pixel row. Real layouts are messier: two boxes meant to share a line are often off by a pixel or three, so you can't group rows by an exact y. The twist here is the rule for "same row": two boxes belong to the same row when their vertical extents overlap, which tolerates that small misalignment. Each box is a plain data object with coordinates — not a real DOM node — so there is no layout engine to lean on; the grouping is yours to compute from the numbers.

Signature

type Box = {
  id: string;      // a label so you can identify each box in the output.
  x: number;       // distance from the left edge of the canvas, in pixels.
  y: number;       // distance from the top edge of the canvas, in pixels.
  width: number;   // box width, in pixels.
  height: number;  // box height, in pixels — defines the box's vertical extent.
};

// Returns a NEW array of the SAME box objects, ordered for reading.
// Does not mutate the input array or any box.
function readingOrderII(elements: Box[]): Box[];

Two boxes a and b share a row when their vertical extents overlap:

a.y < b.y + b.height && b.y < a.y + a.height

Examples

// Two boxes nudged out of alignment (y of 0 and 3) still overlap vertically,
// so they read as one row, left to right by x.
readingOrderII([
  { id: 'right', x: 200, y: 3, width: 40, height: 20 },
  { id: 'left', x: 0, y: 0, width: 40, height: 20 },
]);
// → ids in order: ['left', 'right']
// A clear vertical gap (top ends at y:20, bottom starts at y:60) means two rows.
// The whole top row comes out before the bottom row.
readingOrderII([
  { id: 'A', x: 0, y: 0, width: 40, height: 20 },
  { id: 'B', x: 100, y: 0, width: 40, height: 20 },
  { id: 'C', x: 0, y: 60, width: 40, height: 20 },
  { id: 'D', x: 100, y: 60, width: 40, height: 20 },
]);
// → ids in order: ['A', 'B', 'C', 'D']

Notes

  • Same row means overlapping vertical extents, not equal y. Use a.y < b.y + b.height && b.y < a.y + a.height. This is what lets a row survive a few pixels of misalignment — the defining difference from the exact-row base problem.
  • The test is strict. Boxes whose edges merely touch — one ends exactly where the next begins (a.y + a.height === b.y) — do not overlap, so they are different rows.
  • Within a row, order by x ascending. A box higher on the page reads before a box lower down only when they are in different rows; inside one row, horizontal position decides.
  • Return the same box objects in a new array. Don't clone the boxes and don't reorder the caller's array — build a fresh result that references the original objects.
  • The result is order-independent. Shuffling the input must not change the output; you derive the order from coordinates, never from input position.
  • Don't worry about horizontal overlap, z-index, or text inside the boxes. Vertical extent decides the row; x orders within it. Empty input returns [].
Loading editor…