From ad07adfb5f55937597be9640fbba168aff7dd1c2 Mon Sep 17 00:00:00 2001 From: Aaron Gutierrez Date: Wed, 19 Jun 2024 13:13:43 -0700 Subject: [PATCH] Dynamic programming for even rows --- src/components/grid.tsx | 111 +++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/src/components/grid.tsx b/src/components/grid.tsx index 6fcff8d..6e14196 100644 --- a/src/components/grid.tsx +++ b/src/components/grid.tsx @@ -11,7 +11,7 @@ export interface Props { height: number; } -export const ROW_HEIGHT = 250; +export const ROW_HEIGHT = 350; export const MOBILE_ROW_HEIGHT = 150; interface Row { @@ -19,38 +19,101 @@ interface Row { width: number; } +interface BadList { + splits: number[]; + badness: number; +} + export class Grid extends React.PureComponent { static displayName: string = "Grid"; private gridHeight = 0; + static badness = ( + row: Model.Image[], + width: number, + height: number + ): number => { + const rowWidth = row.reduce((w, img) => w + img.width / img.height, 0); + const rowHeight = width / rowWidth; + + return (rowHeight - height) * (rowHeight - height); + }; + + // [width][idx] -> badness + private rowsMemo: Map> = new Map(); + + rows(idx: number): BadList { + const targetHeight = this._rowHeight(); + + const memo = this.rowsMemo.get(this.props.width) ?? new Map(); + const maybeMemo = memo.get(idx); + + if (maybeMemo) { + return maybeMemo; + } + + if (idx === this.props.images.length) { + return { + splits: [], + badness: 0, + }; + } + if (idx === this.props.images.length - 1) { + const img = this.props.images[idx]; + const h = (img.height * this.props.width) / img.width; + return { + splits: [], + badness: (targetHeight - h) * (targetHeight - h), + }; + } + + let bestIdx = -1; + let leastBad = 1e50; + let bestSplits: number[] = []; + + for (let i = idx + 1; i <= this.props.images.length; i++) { + const rowBadness = Grid.badness( + this.props.images.slice(idx, i), + this.props.width, + targetHeight + ); + const rest = this.rows(i); + const badness = rest.badness + rowBadness; + if (badness < leastBad) { + leastBad = badness; + bestIdx = i; + bestSplits = [i, ...rest.splits]; + } + } + + const badList = { + splits: bestSplits, + badness: leastBad, + }; + + memo.set(idx, badList); + + if (!this.rowsMemo.has(this.props.width)) { + this.rowsMemo.set(this.props.width, memo); + } + + return badList; + } + render() { this.gridHeight = 0; - let row: Model.Image[] = []; - const rows: Row[] = []; - let rowWidth = 0; + const badList = this.rows(0); + let lastBreak = 0; + const rows: Row[] = badList.splits.map((split) => { + const images = this.props.images.slice(lastBreak, split); + lastBreak = split; - this.props.images.forEach((image) => { - const newWidth = rowWidth + image.width / image.height; - const height = this.props.width / newWidth; - - if (height < this._rowHeight()) { - rows.push({ - images: row, - width: rowWidth, - }); - - row = []; - rowWidth = image.width / image.height; - } else { - rowWidth = newWidth; - } - row.push(image); - }); - rows.push({ - images: row, - width: rowWidth, + return { + images, + width: images.reduce((acc, img) => acc + img.width / img.height, 0), + }; }); const images = rows.map((row) => {