Dynamic programming for even rows
This commit is contained in:
@@ -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<Props, {}> {
|
||||
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<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() {
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user