有向无环图的平面计算

时间:2019-01-28 10:17:51

标签: javascript typescript graph

我正在尝试创建图库视图,我在某种程度上遵循Google相册示例,但是无法完美地创建所有代码。

这是我的current implementation (StackBlitz)。我怀疑我在兑现方面有错误的逻辑。

代码实际上已经完成,但是似乎执行的步骤比预期的要多。

ComputeGraph.ts

export function computeGraph<T extends object, K extends { width: number, height: number, original: T }>(FSFiles: K[], idealRowHeight: number, rowWidth: number): GraphCell<T>[][] {
    if (!FSFiles.length) {
        return [];
    }

    const createPossibleRow = rowGenerator<T>(rowWidth);
    const simpleFiles: ReadonlyArray<GraphCell<T>> = FSFiles.map(file => {
        //  This should be 1, but if image was not resized to gallery optimal height, this will then be other and thus should affect badness
        const transformRatio = idealRowHeight / file.height;
        return {
            badness: transformRatio,
            height: idealRowHeight,
            width: file.width * transformRatio,
            original: file.original,
        };
    });

    const possibleGraphRows = createPossibleRow(simpleFiles);
    const graph = new Graph(possibleGraphRows);

    {
        let lastRow: GraphRow<T>;
        //  We can assert type, because if `.pop()` would return `undefined` it would not pass truthy condition.
        while (lastRow = possibleGraphRows.pop() as GraphRow<T>) {
            const remainingCells = simpleFiles.slice(simpleFiles.findIndex(file => {
                return file.original == lastRow.cells[lastRow.cells.length - 1].original;
            }) + 1);
            const newPossibleRow = createPossibleRow(remainingCells);

            graph.addNode(lastRow, newPossibleRow);
            possibleGraphRows.push(...newPossibleRow.filter(row => !row.final));
        }
    }

    return graph.solve().map(row => row.cells);
}

Graph.ts

export class Graph<T> {
    /** parent-child dictionary */
    private nodes: WeakMap<GraphRow<T>, GraphRow<T>[]> = new WeakMap();
    private startingNodes: GraphRow<T>[];

    constructor(startingNodes: GraphRow<T>[]) {
        this.startingNodes = Array.from(startingNodes);
    }

    addNode(node: GraphRow<T>, children: GraphRow<T>[]) {
        if (this.nodes.has(node)) {
            console.error('how?', this.nodes.get(node)! == children);
        }

        this.nodes.set(node, children);
    }

    solve(): GraphRow<T>[] {
        const toVisit = new Set<{ parent: GraphRow<T>, nodeToVisit: GraphRow<T> }>(this.startingNodes.map(parent => {
            return this.nodes.get(parent)!.map(nodeToVisit => ({
                nodeToVisit,
                parent,
            }));
        }).flat());
        const paths = new WeakMap<GraphRow<T>, GraphRow<T>[]>(this.startingNodes.map(node => [node, [node]] as [GraphRow<T>, [GraphRow<T>]]));
        const endNodes = new Map<GraphRow<T>, number>();

        for (const { parent, nodeToVisit } of toVisit) {
            const path = [...paths.get(parent)!, nodeToVisit];
            paths.set(nodeToVisit, path);

            if (nodeToVisit.final) {
                endNodes.set(nodeToVisit, path.reduce((totalBadness, node) => totalBadness + node.badness, 0));
            } else {
                const children = this.nodes.get(nodeToVisit)!;
                children.forEach(node => {
                    toVisit.add({
                        parent: nodeToVisit,
                        nodeToVisit: node,
                    });
                });
            }
        }
        return Array.from(endNodes).reduce((bestCandidate, [node, badness]) => {
            if (badness < bestCandidate.badness) {
                bestCandidate.badness = badness;
                bestCandidate.path = paths.get(node)!;
            }

            return bestCandidate;
        }, {
            path: [] as GraphRow<T>[],
            badness: Infinity,
        }).path;
    }
}

界面(2个文件)

export interface GraphCell<T> {
    badness: number;
    height: number;
    width: number;
    original: T;
}

export interface GraphRow<T> {
    readonly cells: GraphCell<T>[];
    readonly width: number;
    readonly badness: number;
    complete: boolean;
    final: boolean;
}

rowGenerator.ts

export function rowGenerator<T extends object>(rowWidth: number) {
    const rowStore = new WeakMap<T, GraphRow<T>[]>();

    /**
     * Creates ***all*** acceptable rows, that begins with the same file.
     */
    return function createPossibleRow(files: ReadonlyArray<GraphCell<T>>): GraphRow<T>[] {
        const reversedFiles = Array.from(files).reverse();
        let file = reversedFiles.pop();
        //console.log(`Creating row(s) starting with file:`, file);

        if (!file) {
            return [];
        }

        {
            if (rowStore.has(file.original)) {
                return rowStore.get(file.original)!;
            }
        }
        const result: GraphRow<T>[] = [];

        const rowFiles = [];
        let width = 0;
        let complete = false;
        let final = false;

        rowStore.set(file.original, result);

        while (file) {
            rowFiles.push(file);
            width += file.width;

            if (width >= rowWidth) {
                complete = true;
                break;
            }

            file = reversedFiles.pop();
        }

        if (complete) {
            if (rowFiles.length > 1) {
                result.push(createRow(rowWidth, complete, final, rowFiles.slice(0, -1)));
            }
        } else {
            final = true;
        }

        if (!reversedFiles.length) {
            final = true;
        }

        result.push(createRow(rowWidth, complete, final, rowFiles));

        return result;
    };
}

function createRow<T>(rowWidth: number, complete: boolean, final: boolean, files: GraphCell<T>[]): GraphRow<T> {
    const width = files.reduce((acc, file) => acc + file.width, 0);
    const ratio = width / rowWidth;

    return {
        complete,
        final,
        width,
        badness: files.reduce((acc, file) => acc + (file.badness * ratio), 0) / files.length,
        cells: files.map(({ badness, original, ...dimensions }) => ({
            badness,
            original,
            height: dimensions.height / ratio,
            width: dimensions.width / ratio,
        })),
    };
}

Original article Graph example

有人可以帮助我找到我错过的东西吗?为什么会有这么多的递归调用?

0 个答案:

没有答案