Skip to content

Commit

Permalink
Merge pull request #447 from datafold/apply-args-by-chunks
Browse files Browse the repository at this point in the history
Prevent `Maximum call stack exceeded` by apply args to `Math.min`, `Math.max` in chunks
  • Loading branch information
rustedgrail authored Aug 15, 2024
2 parents 295b3e1 + fee1acf commit 71a8f04
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 14 deletions.
3 changes: 2 additions & 1 deletion lib/nesting-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ module.exports = {
function run(g) {
let root = util.addDummyNode(g, "root", {}, "_root");
let depths = treeDepths(g);
let height = Math.max(...Object.values(depths)) - 1; // Note: depths is an Object not an array
let depthsArr = Object.values(depths);
let height = util.applyWithChunking(Math.max, depthsArr) - 1; // Note: depths is an Object not an array
let nodeSep = 2 * height + 1;

g.graph().nestingRoot = root;
Expand Down
3 changes: 2 additions & 1 deletion lib/order/init-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ module.exports = initOrder;
function initOrder(g) {
let visited = {};
let simpleNodes = g.nodes().filter(v => !g.children(v).length);
let maxRank = Math.max(...simpleNodes.map(v => g.node(v).rank));
let simpleNodesRanks = simpleNodes.map(v => g.node(v).rank);
let maxRank = util.applyWithChunking(Math.max, simpleNodesRanks);
let layers = util.range(maxRank + 1).map(() => []);

function dfs(v) {
Expand Down
8 changes: 4 additions & 4 deletions lib/position/bk.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ function findSmallestWidthAlignment(g, xss) {
*/
function alignCoordinates(xss, alignTo) {
let alignToVals = Object.values(alignTo),
alignToMin = Math.min(...alignToVals),
alignToMax = Math.max(...alignToVals);
alignToMin = util.applyWithChunking(Math.min, alignToVals),
alignToMax = util.applyWithChunking(Math.max, alignToVals);

["u", "d"].forEach(vert => {
["l", "r"].forEach(horiz => {
Expand All @@ -324,9 +324,9 @@ function alignCoordinates(xss, alignTo) {
if (xs === alignTo) return;

let xsVals = Object.values(xs);
let delta = alignToMin - Math.min(...xsVals);
let delta = alignToMin - util.applyWithChunking(Math.min, xsVals);
if (horiz !== "l") {
delta = alignToMax - Math.max(...xsVals);
delta = alignToMax - util.applyWithChunking(Math.max,xsVals);
}

if (delta) {
Expand Down
8 changes: 6 additions & 2 deletions lib/rank/util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use strict";

const { applyWithChunking } = require("../util");

module.exports = {
longestPath: longestPath,
slack: slack
Expand Down Expand Up @@ -36,13 +38,15 @@ function longestPath(g) {
}
visited[v] = true;

var rank = Math.min(...g.outEdges(v).map(e => {
let outEdgesMinLens = g.outEdges(v).map(e => {
if (e == null) {
return Number.POSITIVE_INFINITY;
}

return dfs(e.w) - g.edge(e).minlen;
}));
});

var rank = applyWithChunking(Math.min, outEdgesMinLens);

if (rank === Number.POSITIVE_INFINITY) {
rank = 0;
Expand Down
37 changes: 31 additions & 6 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ let Graph = require("@dagrejs/graphlib").Graph;
module.exports = {
addBorderNode,
addDummyNode,
applyWithChunking,
asNonCompoundGraph,
buildLayerMatrix,
intersectRect,
Expand Down Expand Up @@ -153,14 +154,15 @@ function buildLayerMatrix(g) {
* rank(v) >= 0 and at least one node w has rank(w) = 0.
*/
function normalizeRanks(g) {
let min = Math.min(...g.nodes().map(v => {
let nodeRanks = g.nodes().map(v => {
let rank = g.node(v).rank;
if (rank === undefined) {
return Number.MAX_VALUE;
}

return rank;
}));
});
let min = applyWithChunking(Math.min, nodeRanks);
g.nodes().forEach(v => {
let node = g.node(v);
if (node.hasOwnProperty("rank")) {
Expand All @@ -171,7 +173,8 @@ function normalizeRanks(g) {

function removeEmptyRanks(g) {
// Ranks may not start at 0, so we need to offset them
let offset = Math.min(...g.nodes().map(v => g.node(v).rank));
let nodeRanks = g.nodes().map(v => g.node(v).rank);
let offset = applyWithChunking(Math.min, nodeRanks);

let layers = [];
g.nodes().forEach(v => {
Expand Down Expand Up @@ -205,15 +208,37 @@ function addBorderNode(g, prefix, rank, order) {
return addDummyNode(g, "border", node, prefix);
}

function splitToChunks(array, chunkSize = CHUNKING_THRESHOLD) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
const chunk = array.slice(i, i + chunkSize);
chunks.push(chunk);
}
return chunks;
}

const CHUNKING_THRESHOLD = 65535;

function applyWithChunking(fn, argsArray) {
if(argsArray.length > CHUNKING_THRESHOLD) {
const chunks = splitToChunks(argsArray);
return fn.apply(null, chunks.map(chunk => fn.apply(null, chunk)));
} else {
return fn.apply(null, argsArray);
}
}

function maxRank(g) {
return Math.max(...g.nodes().map(v => {
const nodes = g.nodes();
const nodeRanks = nodes.map(v => {
let rank = g.node(v).rank;
if (rank === undefined) {
return Number.MIN_VALUE;
}

return rank;
}));
});

return applyWithChunking(Math.max, nodeRanks);
}

/*
Expand Down

0 comments on commit 71a8f04

Please sign in to comment.