From 74d1f2be23e6f9e91ff61f60562bfff5fe6d7427 Mon Sep 17 00:00:00 2001 From: NunoSempere Date: Tue, 7 Dec 2021 20:40:43 +0100 Subject: [PATCH] feat: Produce an O(n) to O(log2(n)) improvement in findPaths Details: The findPathsInner function in lib/findPaths.js is too expensive, and has a tendency to throw "too much recursion" errors. However, it can be optimized. In particular, instead of just going through all paths, we could go in the paths in the right direction. Note that: The current improvements don't do that yet. I was trying to do that at the findDistance level, but I was being dumb. --- lib/comparisonView.js | 13 +-- lib/findPaths.js | 222 ++++++++++++++++++++++++------------------ lib/labeledgraph.js | 6 +- lib/pushToMongo.js | 2 + 4 files changed, 141 insertions(+), 102 deletions(-) diff --git a/lib/comparisonView.js b/lib/comparisonView.js index 025d302..f6489c4 100644 --- a/lib/comparisonView.js +++ b/lib/comparisonView.js @@ -1,6 +1,3 @@ -/* Definitions */ -const elementsDocument = null// - /* Imports */ import Head from 'next/head' import React, { useState } from "react"; @@ -13,6 +10,9 @@ import { TextAreaForJson } from "./textAreaForJson" import { pushToMongo } from "./pushToMongo.js" import { maxMergeSortSteps, expectedNumMergeSortSteps } from "./utils.js" +/* DEFINTIONS */ +const DEFAULT_COMPARE = 2 // 1, unless you're testing smth. + /* Helpers */ let increasingList = (n) => Array.from(Array(n).keys()) let buildLinks = quantitativeComparisons => quantitativeComparisons.map(([element1, element2, distance, reasoning]) => ({ source: element1, target: element2, distance: distance, reasoning: reasoning })) @@ -76,7 +76,7 @@ export default function ComparisonView({ listOfElementsForView }) { let [showAdvancedOptions, changeShowAdvanceOptions] = useState(initialShowAdvancedOptions); let [showComparisons, changeShowComparisons] = useState(initialShowComparisons); let [showChangeDataSet, changeshowChangeDataSet] = useState(initialShowChangeDataSet); - let [numSteps, increaseNumSteps] = useState(initialNumSteps); + let [numSteps, changeNumSteps] = useState(initialNumSteps); let [maxSteps, changeMaxSteps] = useState(initialMaxSteps) let [expectedSteps, changeExpectedSteps] = useState(initialExpectedSteps) @@ -87,6 +87,7 @@ export default function ComparisonView({ listOfElementsForView }) { setQuantitativeComparisons(initialQuantitativeComparisons) setIsListOrdered(initialIsListOdered) setOrderedList(initialOrderedList) + changeNumSteps(0) removeOldSvg() } @@ -203,9 +204,9 @@ export default function ComparisonView({ listOfElementsForView }) { let newQuantitativeComparisons = [...quantitativeComparisons, newQuantitativeComparison] setQuantitativeComparisons(newQuantitativeComparisons) - setSliderValue(1) + setSliderValue(DEFAULT_COMPARE) setReasoning('') - increaseNumSteps(numSteps + 1) + changeNumSteps(numSteps + 1) if (successStatus) { let jsObject = nicelyFormatLinks(quantitativeComparisons, listOfElements) await pushToMongo(jsObject) diff --git a/lib/findPaths.js b/lib/findPaths.js index 6c8f4c3..1a5f324 100644 --- a/lib/findPaths.js +++ b/lib/findPaths.js @@ -3,72 +3,66 @@ import React from "react"; import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "../lib/utils.js" /* Utilities */ -let avg = arr => arr.reduce((a,b) => (a+b)) / arr.length +let avg = arr => arr.reduce((a, b) => (a + b), 0) / arr.length /* Main function */ -function findPathsInner({sourceElementId, targetElementId, pathSoFar, links, nodes, maxLengthOfPath}){ - // THis has a tendency to produce too much recursion errors - // could be refactored +function findPathsInner({ sourceElementId, targetElementId, pathSoFar, links, nodes, maxLengthOfPath }) { let paths = [] - if(maxLengthOfPath > 0){ - for(let link of links){ - if( - ((link.source == sourceElementId) && (link.target == targetElementId)) || + if (maxLengthOfPath > 0) { + for (let link of links) { + if ( + ((link.source == sourceElementId) && (link.target == targetElementId)) || ((link.source == targetElementId) && (link.target == sourceElementId)) - ){ + ) { paths.push(pathSoFar.concat(link).flat()) - } else if((link.source == sourceElementId)){ - let newPaths = findPathsInner({sourceElementId:link.target, targetElementId, pathSoFar: pathSoFar.concat(link).flat(), links, nodes, maxLengthOfPath: (maxLengthOfPath-1)}) - if(newPaths.length != 0){ + } else if ((link.source == sourceElementId)) { + let newPaths = findPathsInner({ sourceElementId: link.target, targetElementId, pathSoFar: pathSoFar.concat(link).flat(), links, nodes, maxLengthOfPath: (maxLengthOfPath - 1) }) + if (newPaths.length != 0) { paths.push(...newPaths) } - }else if((link.target == sourceElementId)){ - let newPaths = findPathsInner({sourceElementId:link.source, targetElementId, pathSoFar: pathSoFar.concat(link).flat(), links, nodes, maxLengthOfPath: (maxLengthOfPath-1)}) - if(newPaths.length != 0){ + } else if ((link.target == sourceElementId)) { + let newPaths = findPathsInner({ sourceElementId: link.source, targetElementId, pathSoFar: pathSoFar.concat(link).flat(), links, nodes, maxLengthOfPath: (maxLengthOfPath - 1) }) + if (newPaths.length != 0) { paths.push(...newPaths) } } } } - + return paths } -function findPaths({sourceElementId, targetElementId, links, nodes}){ +function findPaths({ sourceElementId, targetElementId, links, nodes }) { let positionSourceElement = nodes.map((element, i) => (element.id)).indexOf(sourceElementId) let positionTargetElement = nodes.map((element, i) => (element.id)).indexOf(targetElementId) let maxLengthOfPath = Math.abs(positionSourceElement - positionTargetElement) - - let paths = [] - try{ - paths = findPathsInner({sourceElementId, targetElementId, pathSoFar: [], links, nodes, maxLengthOfPath}) - }catch(error){ - console.log("Error: probably too much recursion.") - } - return paths + + return findPathsInner({ sourceElementId, targetElementId, pathSoFar: [], links, nodes, maxLengthOfPath }) } -function findDistance({sourceElementId, targetElementId, nodes, links}){ - let paths = findPaths({sourceElementId, targetElementId, nodes, links}) +function findDistance({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, nodes, links, direction }) { + let paths = findPaths({ sourceElementId, targetElementId, nodes, links }) + console.log(`findDistance from ${sourceElementPosition} to ${targetElementPosition}`) console.log(targetElementId) + console.log(direction) console.log(paths) let weights = [] - for(let path of paths){ + for (let path of paths) { let currentSource = sourceElementId let weight = 1 - for(let element of path){ + for (let element of path) { let distance = 0 - if(element.source == currentSource){ + if (element.source == currentSource) { distance = element.distance currentSource = element.target - }else if(element.target == currentSource){ - distance = 1/Number(element.distance) + } else if (element.target == currentSource) { + distance = 1 / Number(element.distance) currentSource = element.source } - weight = weight*distance - + weight = weight * distance + } weights.push(weight) } @@ -78,119 +72,161 @@ function findDistance({sourceElementId, targetElementId, nodes, links}){ //return weights.map(weight => Math.round(weight*100)/100) } -function findDistancesForAllElements({nodes, links}){ +function getDirectionalLinks({ nodes, links }) { + console.log("getDirectionalLinks") + // direction: 1 for upwards, -1 for downwards + let upwardsLinks = [] + let downwardsLinks = [] + links.forEach(link => { + console.log(link) + let sourceElementId = link.source + let targetElementId = link.target + if (link.distance < 1) { + // We already deal with this case upstream, but whatever. + [sourceElementId, targetElementId] = [targetElementId, sourceElementId] + } + let sourceElementPosition = nodes.find(element => element.id == sourceElementId).position + let targetElementPosition = nodes.find(element => element.id == targetElementId).position + if (link.distance == 1) { + // If two elements are the same, then they belong to both upwards and downwards paths!! + upwardsLinks.push(link) + downwardsLinks.push(link) + } else if (sourceElementPosition < targetElementPosition) { + upwardsLinks.push(link) + } else { + downwardsLinks.push(link) + } + }) + console.log([upwardsLinks, downwardsLinks]) + return [upwardsLinks, downwardsLinks] +} + +function findDistancesForAllElements({ nodes, links }) { let referenceElements = nodes.filter(x => x.isReferenceValue) - let midpoint = Math.round(nodes.length/2) + let midpoint = Math.round(nodes.length / 2) let referenceElement = referenceElements.length > 0 ? referenceElements[0] : nodes[midpoint] // console.log(nodes) - let distances = nodes.map(node => - node.isReferenceValue || (node.id == referenceElement.id) ? [1] : findDistance({sourceElementId: referenceElement.id, targetElementId: node.id, nodes, links}) - ) + let [upwardsLinks, downwardsLinks] = getDirectionalLinks({ nodes, links }) + console.log(`referenceElement.position: ${referenceElement.position}`) + let distances = nodes.map(node => { + if (node.isReferenceValue || (node.id == referenceElement.id)) { + return [1] + } else { + console.log("node") + console.log(node) + let isUpwardsDirection = referenceElement.position < node.position; + let distance = findDistance({ + sourceElementId: referenceElement.id, + sourceElementPosition: referenceElement.position, + targetElementId: node.id, + targetElementPosition: node.position, + nodes: nodes, + links: links, // isUpwardsDirection ? upwardsLinks : downwardsLinks, // links + direction: isUpwardsDirection ? "upwards" : "downwards" + }) + return distance + } + }) return distances } -function abridgeArrayAndDisplay(array){ +function abridgeArrayAndDisplay(array) { let newArray let formatForDisplay - if(array.length > 10){ - newArray = array.slice(0,9) - formatForDisplay= newArray.map(d => formatLargeOrSmall(d)) + if (array.length > 10) { + newArray = array.slice(0, 9) + formatForDisplay = newArray.map(d => formatLargeOrSmall(d)) formatForDisplay[9] = "..." - }else{ - newArray=array - formatForDisplay= newArray.map(d => formatLargeOrSmall(d)) + } else { + newArray = array + formatForDisplay = newArray.map(d => formatLargeOrSmall(d)) } let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "") return result } -function CreateTableWithDistances({isListOrdered, orderedList, listOfElements, links}){ - // Not used anywhere because it's too resource intensive - // The culprit is findPathsInner, a recursive function which - // has a tendency to produce "Maximum call stack size exceeded" - // or "too much recursion" errors - if(!isListOrdered || orderedList.length < listOfElements.length){ +export function CreateTableWithDistances({ isListOrdered, orderedList, listOfElements, links }) { + if (!isListOrdered || orderedList.length < listOfElements.length) { return (
{""}
) } else { - let nodes = orderedList.map(i => listOfElements[i]) - let distances = findDistancesForAllElements({nodes, links}) - let rows = nodes.map((element, i) => ({id: numToAlphabeticalString(element.id), name: element.name, distances: distances[i]})) + let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) + let distances = findDistancesForAllElements({ nodes, links }) + let rows = nodes.map((element, i) => ({ id: numToAlphabeticalString(element.position), position: element.position, name: element.name, distances: distances[i] })) console.log("rows@CreateTableWithDistances") console.log(rows) - return( + return (
- - - - - - - - - + + + + + + + + + + + - {rows.filter(row => row.distances.length > 0).map(row => - + {rows.map(row => + + + - - )} - 0 ? "hidden": ""}> - "Maximum compute exceeded, rely on the graph instead" - - {} + + )}
Id   Element    Possible relative values    Average relative value
Id   Position   Element    Possible relative values    Average relative value
{row.id}
{row.id}   {row.position}     {row.name}     {abridgeArrayAndDisplay(row.distances)}     {formatLargeOrSmall(avg(row.distances))}
) } - + } - -function CreateTableWithoutDistances({isListOrdered, orderedList, listOfElements, links}){ - if(!isListOrdered || orderedList.length < listOfElements.length){ +function CreateTableWithoutDistances({ isListOrdered, orderedList, listOfElements, links }) { + if (!isListOrdered || orderedList.length < listOfElements.length) { return (
{""}
) } else { let nodes = orderedList.map(i => listOfElements[i]) - let rows = nodes.map((element, i) => ({id: numToAlphabeticalString(element.id), name: element.name})) + let rows = nodes.map((element, i) => ({ id: numToAlphabeticalString(element.id), name: element.name })) console.log("rows@CreateTableWithoutDistances") console.log(rows) - return( + return (
- - - - - - + + + + + + - {rows.map(row => - + {rows.map(row => + - - )} - {} + + )} + { }
Id   Element    
Id   Element    
{row.id}
{row.id}     {row.name}
) } - + } -export const CreateTable = CreateTableWithoutDistances; +export const CreateTable = CreateTableWithDistances // CreateTableWithoutDistances; /* Testing */ //import fs from 'fs'; @@ -203,7 +239,7 @@ let nodes = JSON.parse(fs.readFileSync(path.join(directory, 'listOfPosts.json'), let paths = findPathsInner({sourceElementId:2, targetElementId:0, pathSoFar: [], links, nodes, maxLengthOfPath: 2}) console.log(JSON.stringify(paths, null, 2)) */ -/* +/* let paths = findPaths({sourceElementId:2, targetElementId:0, links, nodes}) console.log(JSON.stringify(paths, null, 2)) */ diff --git a/lib/labeledgraph.js b/lib/labeledgraph.js index dfe0b04..3259dd3 100644 --- a/lib/labeledgraph.js +++ b/lib/labeledgraph.js @@ -67,7 +67,7 @@ function drawGraphInner({ nodes, links }) { .append("text") .attr("x", function (d) { return (x(d.id)) }) .attr("y", height - 10) - .text(function (d) { return numToAlphabeticalString(d.id) }) + .text(function (d) { return numToAlphabeticalString(d.position) }) .style("text-anchor", "middle") // Add the links @@ -119,7 +119,7 @@ function drawGraphInner({ nodes, links }) { }) .text(function (d) { return formatLargeOrSmall(Number(d.distance)) - return (truncateValueForDisplay(Number(d.distance))) + // return (truncateValueForDisplay(Number(d.distance))) //return(Number(d.distance).toPrecision(2).toString()) }) .style("text-anchor", "middle") @@ -127,7 +127,7 @@ function drawGraphInner({ nodes, links }) { export function DrawGraph({ isListOrdered, orderedList, listOfElements, links }) { if (isListOrdered) { - let nodes = orderedList.map(i => listOfElements[i]) + let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) drawGraphInner({ nodes, links }); } return ( diff --git a/lib/pushToMongo.js b/lib/pushToMongo.js index 6f0424a..7bb7e93 100644 --- a/lib/pushToMongo.js +++ b/lib/pushToMongo.js @@ -1,9 +1,11 @@ import axios from "axios" export async function pushToMongo(data){ + /* let response = await axios.post('http://metaforecast-twitter-bot.herokuapp.com/utility-function-extractor', { data: data }) console.log(response) + */ } // pushToMongo() \ No newline at end of file