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.
This commit is contained in:
NunoSempere 2021-12-07 20:40:43 +01:00
parent f8791450a2
commit 74d1f2be23
4 changed files with 141 additions and 102 deletions

View File

@ -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)

View File

@ -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 (<div>{""}</div>)
} 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 (
<div>
<table className="">
<thead >
<tr >
<th >Id</th>
<th>&nbsp;&nbsp;&nbsp;</th>
<th >Element</th>
<th> &nbsp;&nbsp;&nbsp;</th>
<th >Possible relative values</th>
<th> &nbsp;&nbsp;&nbsp;</th>
<th >Average relative value</th>
</tr>
<tr >
<th >Id</th>
<th>&nbsp;&nbsp;&nbsp;</th>
<th >Position</th>
<th>&nbsp;&nbsp;&nbsp;</th>
<th >Element</th>
<th> &nbsp;&nbsp;&nbsp;</th>
<th >Possible relative values</th>
<th> &nbsp;&nbsp;&nbsp;</th>
<th >Average relative value</th>
</tr>
</thead>
<tbody>
{rows.filter(row => row.distances.length > 0).map(row => <tr key={row.id}>
<td className="" >{row.id}</td>
{rows.map(row => <tr key={row.id}>
<td className="" >{row.id}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="" >{row.position}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{row.name}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{abridgeArrayAndDisplay(row.distances)}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{formatLargeOrSmall(avg(row.distances))}</td>
</tr>
)}
<tr className={rows[0].distances.length > 0 ? "hidden": ""}>
"Maximum compute exceeded, rely on the graph instead"
</tr>
{}
</tr>
)}
</tbody>
</table>
</div>
)
}
}
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 (<div>{""}</div>)
} 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 (
<div>
<table className="">
<thead >
<tr >
<th >Id</th>
<th>&nbsp;&nbsp;&nbsp;</th>
<th >Element</th>
<th> &nbsp;&nbsp;&nbsp;</th>
</tr>
<tr >
<th >Id</th>
<th>&nbsp;&nbsp;&nbsp;</th>
<th >Element</th>
<th> &nbsp;&nbsp;&nbsp;</th>
</tr>
</thead>
<tbody>
{rows.map(row => <tr key={row.id}>
<td className="" >{row.id}</td>
{rows.map(row => <tr key={row.id}>
<td className="" >{row.id}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{row.name}</td>
</tr>
)}
{}
</tr>
)}
{ }
</tbody>
</table>
</div>
)
}
}
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))
*/

View File

@ -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 (

View File

@ -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()