Dynamic programming for even rows

This commit is contained in:
2024-06-19 13:13:43 -07:00
parent f9c0b9ae09
commit ad07adfb5f

View File

@@ -11,7 +11,7 @@ export interface Props {
height: number; height: number;
} }
export const ROW_HEIGHT = 250; export const ROW_HEIGHT = 350;
export const MOBILE_ROW_HEIGHT = 150; export const MOBILE_ROW_HEIGHT = 150;
interface Row { interface Row {
@@ -19,38 +19,101 @@ interface Row {
width: number; width: number;
} }
interface BadList {
splits: number[];
badness: number;
}
export class Grid extends React.PureComponent<Props, {}> { export class Grid extends React.PureComponent<Props, {}> {
static displayName: string = "Grid"; static displayName: string = "Grid";
private gridHeight = 0; 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<number, Map<number, BadList>> = 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() { render() {
this.gridHeight = 0; this.gridHeight = 0;
let row: Model.Image[] = []; const badList = this.rows(0);
const rows: Row[] = []; let lastBreak = 0;
let rowWidth = 0; const rows: Row[] = badList.splits.map((split) => {
const images = this.props.images.slice(lastBreak, split);
lastBreak = split;
this.props.images.forEach((image) => { return {
const newWidth = rowWidth + image.width / image.height; images,
const height = this.props.width / newWidth; width: images.reduce((acc, img) => acc + img.width / img.height, 0),
};
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,
}); });
const images = rows.map((row) => { const images = rows.map((row) => {