From 5ec31fa36524eb072676674adfba0485d897640a Mon Sep 17 00:00:00 2001 From: NunoSempere Date: Sat, 29 Jan 2022 16:06:16 -0500 Subject: [PATCH] fix: Formatting + avg => geomMean --- lib/findPaths.js | 530 ++++++++++++++++++++++++-------------------- lib/labeledGraph.js | 280 ++++++++++++----------- lib/utils.js | 134 ++++++----- 3 files changed, 515 insertions(+), 429 deletions(-) diff --git a/lib/findPaths.js b/lib/findPaths.js index 058c56a..cf33036 100644 --- a/lib/findPaths.js +++ b/lib/findPaths.js @@ -1,285 +1,328 @@ /* Imports*/ -import React from 'react'; -import { numToAlphabeticalString, formatLargeOrSmall, avg } from "../lib/utils.js" +import React from "react"; +import { + numToAlphabeticalString, + formatLargeOrSmall, + avg, + geomMean, +} from "../lib/utils.js"; /* Functions */ const pathPlusLink = (pathSoFar, link) => { - return [...pathSoFar, link] - // previously: pathSoFar.concat(link).flat() - // Note that concat is not destructive - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat -} + return [...pathSoFar, link]; + // previously: pathSoFar.concat(link).flat() + // Note that concat is not destructive + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat +}; async function findPathsWithoutPrunning({ - sourceElementId, targetElementId, - maxLengthOfPath, pathSoFar, - links, nodes + sourceElementId, + targetElementId, + maxLengthOfPath, + pathSoFar, + links, + nodes, }) { - // This is an un-used function which might make findPaths more understandable - // It uses the same recursive functionality - // but has no path prunning - let paths = [] + // This is an un-used function which might make findPaths more understandable + // It uses the same recursive functionality + // but has no path prunning + let paths = []; - /* Path traversing */ - if (maxLengthOfPath > 0) { - for (let link of links) { // vs let link of linksNow in findPaths - if ( - ((link.source == sourceElementId) && (link.target == targetElementId)) || - ((link.source == targetElementId) && (link.target == sourceElementId)) - ) { // direct Path - let newPath = pathPlusLink(pathSoFar, link) - paths.push(newPath) - } else if ((link.source == sourceElementId)) { - let newPaths = await findPaths({ - pathSoFar: pathPlusLink(pathSoFar, link), - maxLengthOfPath: (maxLengthOfPath - 1), - sourceElementId: link.target, - targetElementId, - links: links, // vs let link of linksInner in findPaths - nodes - }) - if (newPaths.length != 0) { - paths.push(...newPaths) - } - } else if ((link.target == sourceElementId)) { - let newPaths = await findPaths({ - pathSoFar: pathPlusLink(pathSoFar, link), - maxLengthOfPath: (maxLengthOfPath - 1), - sourceElementId: link.source, - targetElementId, - links: links, // vs let link of linksInner in findPaths - nodes - }) - if (newPaths.length != 0) { - paths.push(...newPaths) - } - } + /* Path traversing */ + if (maxLengthOfPath > 0) { + for (let link of links) { + // vs let link of linksNow in findPaths + if ( + (link.source == sourceElementId && link.target == targetElementId) || + (link.source == targetElementId && link.target == sourceElementId) + ) { + // direct Path + let newPath = pathPlusLink(pathSoFar, link); + paths.push(newPath); + } else if (link.source == sourceElementId) { + let newPaths = await findPaths({ + pathSoFar: pathPlusLink(pathSoFar, link), + maxLengthOfPath: maxLengthOfPath - 1, + sourceElementId: link.target, + targetElementId, + links: links, // vs let link of linksInner in findPaths + nodes, + }); + if (newPaths.length != 0) { + paths.push(...newPaths); } + } else if (link.target == sourceElementId) { + let newPaths = await findPaths({ + pathSoFar: pathPlusLink(pathSoFar, link), + maxLengthOfPath: maxLengthOfPath - 1, + sourceElementId: link.source, + targetElementId, + links: links, // vs let link of linksInner in findPaths + nodes, + }); + if (newPaths.length != 0) { + paths.push(...newPaths); + } + } } + } - return paths + return paths; } async function findPaths({ - sourceElementId, sourceElementPosition, - targetElementId, targetElementPosition, - maxLengthOfPath, pathSoFar, - links, nodes + sourceElementId, + sourceElementPosition, + targetElementId, + targetElementPosition, + maxLengthOfPath, + pathSoFar, + links, + nodes, }) { - // This is the key path finding function - // It finds the path from one element to another, recursively - // It used to be very computationally expensive until I added - // the path prunning step: Instead of traversing all links, - // traverse only those which are between the origin and target links - // this requires us to have a notion of "between" + // This is the key path finding function + // It finds the path from one element to another, recursively + // It used to be very computationally expensive until I added + // the path prunning step: Instead of traversing all links, + // traverse only those which are between the origin and target links + // this requires us to have a notion of "between" - let paths = [] + let paths = []; - /* Path prunning*/ - let minPos = Math.min(sourceElementPosition, targetElementPosition) - let maxPos = Math.max(sourceElementPosition, targetElementPosition) - let linksInner = links.filter(link => - (minPos <= link.sourceElementPosition && link.sourceElementPosition <= maxPos) && - (minPos <= link.targetElementPosition && link.targetElementPosition <= maxPos) - ) - let linksNow = linksInner.filter(link => (link.source == sourceElementId || link.target == sourceElementId)) + /* Path prunning*/ + let minPos = Math.min(sourceElementPosition, targetElementPosition); + let maxPos = Math.max(sourceElementPosition, targetElementPosition); + let linksInner = links.filter( + (link) => + minPos <= link.sourceElementPosition && + link.sourceElementPosition <= maxPos && + minPos <= link.targetElementPosition && + link.targetElementPosition <= maxPos + ); + let linksNow = linksInner.filter( + (link) => link.source == sourceElementId || link.target == sourceElementId + ); - /* Path traversing */ - if (maxLengthOfPath > 0) { - for (let link of linksNow) { - if ( - ((link.source == sourceElementId) && (link.target == targetElementId)) || - ((link.source == targetElementId) && (link.target == sourceElementId)) - ) { // direct Path - let newPath = pathPlusLink(pathSoFar, link) - paths.push(newPath) - } else if ((link.source == sourceElementId)) { - let newPaths = await findPaths({ - pathSoFar: pathPlusLink(pathSoFar, link), - maxLengthOfPath: (maxLengthOfPath - 1), - sourceElementPosition: link.sourceElementPosition, - sourceElementId: link.target, - targetElementId, targetElementPosition, - links: linksInner, - nodes - }) - if (newPaths.length != 0) { - paths.push(...newPaths) - } - } else if ((link.target == sourceElementId)) { - let newPaths = await findPaths({ - pathSoFar: pathPlusLink(pathSoFar, link), - maxLengthOfPath: (maxLengthOfPath - 1), - sourceElementPosition: link.sourceElementPosition, - sourceElementId: link.source, - targetElementPosition, - targetElementId, - links: linksInner, - nodes - }) - if (newPaths.length != 0) { - paths.push(...newPaths) - } - } + /* Path traversing */ + if (maxLengthOfPath > 0) { + for (let link of linksNow) { + if ( + (link.source == sourceElementId && link.target == targetElementId) || + (link.source == targetElementId && link.target == sourceElementId) + ) { + // direct Path + let newPath = pathPlusLink(pathSoFar, link); + paths.push(newPath); + } else if (link.source == sourceElementId) { + let newPaths = await findPaths({ + pathSoFar: pathPlusLink(pathSoFar, link), + maxLengthOfPath: maxLengthOfPath - 1, + sourceElementPosition: link.sourceElementPosition, + sourceElementId: link.target, + targetElementId, + targetElementPosition, + links: linksInner, + nodes, + }); + if (newPaths.length != 0) { + paths.push(...newPaths); } + } else if (link.target == sourceElementId) { + let newPaths = await findPaths({ + pathSoFar: pathPlusLink(pathSoFar, link), + maxLengthOfPath: maxLengthOfPath - 1, + sourceElementPosition: link.sourceElementPosition, + sourceElementId: link.source, + targetElementPosition, + targetElementId, + links: linksInner, + nodes, + }); + if (newPaths.length != 0) { + paths.push(...newPaths); + } + } } + } - return paths + return paths; } async function findDistance({ - sourceElementId, sourceElementPosition, - targetElementId, targetElementPosition, - nodes, links + sourceElementId, + sourceElementPosition, + targetElementId, + targetElementPosition, + nodes, + links, }) { - // This function gets all possible paths using findPaths - // then orders them correctly in the for loop - // (by flipping the distance to 1/distance when necessary) - // and then gets the array of weights for the different paths. - console.log(`findDistance@findPaths.js from ${sourceElementPosition} to ${targetElementPosition}`) + // This function gets all possible paths using findPaths + // then orders them correctly in the for loop + // (by flipping the distance to 1/distance when necessary) + // and then gets the array of weights for the different paths. + console.log( + `findDistance@findPaths.js from ${sourceElementPosition} to ${targetElementPosition}` + ); - let maxLengthOfPath = Math.abs(sourceElementPosition - targetElementPosition) - let paths = await findPaths({ - sourceElementId, sourceElementPosition, - targetElementId, targetElementPosition, - links, nodes, - maxLengthOfPath, pathSoFar: [] - }); + let maxLengthOfPath = Math.abs(sourceElementPosition - targetElementPosition); + let paths = await findPaths({ + sourceElementId, + sourceElementPosition, + targetElementId, + targetElementPosition, + links, + nodes, + maxLengthOfPath, + pathSoFar: [], + }); - let weights = [] - for (let path of paths) { - let currentSource = sourceElementId - let weight = 1 - for (let element of path) { - let distance = 0 - if (element.source == currentSource) { - distance = element.distance - currentSource = element.target - } else if (element.target == currentSource) { - distance = 1 / Number(element.distance) - currentSource = element.source - } - weight = weight * distance - - } - weights.push(weight) + let weights = []; + for (let path of paths) { + let currentSource = sourceElementId; + let weight = 1; + for (let element of path) { + let distance = 0; + if (element.source == currentSource) { + distance = element.distance; + currentSource = element.target; + } else if (element.target == currentSource) { + distance = 1 / Number(element.distance); + currentSource = element.source; + } + weight = weight * distance; } - return weights + weights.push(weight); + } + return weights; } async function findDistancesForAllElements({ nodes, links }) { - // Simple wrapper function around findDistance - // Needs to find the reference point first - console.log("findDistancesForAllElements@findPaths.js") - /* Get or build reference element */ - let referenceElements = nodes.filter(x => x.isReferenceValue) - let midpoint = Math.round(nodes.length / 2) - let referenceElement = referenceElements.length > 0 ? referenceElements[0] : nodes[midpoint] - console.log(`referenceElement.position: ${referenceElement.position}`) + // Simple wrapper function around findDistance + // Needs to find the reference point first + console.log("findDistancesForAllElements@findPaths.js"); + /* Get or build reference element */ + let referenceElements = nodes.filter((x) => x.isReferenceValue); + let midpoint = Math.round(nodes.length / 2); + let referenceElement = + referenceElements.length > 0 ? referenceElements[0] : nodes[midpoint]; + console.log(`referenceElement.position: ${referenceElement.position}`); - /* Get distances. */ - let distances = nodes.map(node => { - if (node.isReferenceValue || (node.id == referenceElement.id)) { - return [1] - } else { - console.log("node") - console.log(node) - let distance = findDistance({ - sourceElementId: referenceElement.id, - sourceElementPosition: referenceElement.position, - targetElementId: node.id, - targetElementPosition: node.position, - nodes: nodes, - links: links, - }) - return distance - } - }) - distances = await Promise.all(distances) - return distances + /* Get distances. */ + let distances = nodes.map((node) => { + if (node.isReferenceValue || node.id == referenceElement.id) { + return [1]; + } else { + console.log("node"); + console.log(node); + let distance = findDistance({ + sourceElementId: referenceElement.id, + sourceElementPosition: referenceElement.position, + targetElementId: node.id, + targetElementPosition: node.position, + nodes: nodes, + links: links, + }); + return distance; + } + }); + distances = await Promise.all(distances); + return distances; } -export async function buildRows({ isListOrdered, orderedList, listOfElements, links, rows, setTableRows }) { - console.log("buildRows@findPaths.js") - // This function is used in pages/comparisonView.js to create the rows that will be displayed. - // it is in there because it needs to be deployed after isListOrdered becomes true, - // and using an useEffect inside CreateTable was too messy. - if (isListOrdered && !(orderedList.length < listOfElements.length) && rows.length == 0) { - let nodes = [] - let positionDictionary = ({}) - orderedList.forEach((id, pos) => { - nodes.push({ ...listOfElements[id], position: pos }) - positionDictionary[id] = pos - }) - links = links.map(link => ({ - ...link, - sourceElementPosition: positionDictionary[link.source], - targetElementPosition: positionDictionary[link.target] - })) +export async function buildRows({ + isListOrdered, + orderedList, + listOfElements, + links, + rows, + setTableRows, +}) { + console.log("buildRows@findPaths.js"); + // This function is used in pages/comparisonView.js to create the rows that will be displayed. + // it is in there because it needs to be deployed after isListOrdered becomes true, + // and using an useEffect inside CreateTable was too messy. + if ( + isListOrdered && + !(orderedList.length < listOfElements.length) && + rows.length == 0 + ) { + let nodes = []; + let positionDictionary = {}; + orderedList.forEach((id, pos) => { + nodes.push({ ...listOfElements[id], position: pos }); + positionDictionary[id] = pos; + }); + links = links.map((link) => ({ + ...link, + sourceElementPosition: positionDictionary[link.source], + targetElementPosition: positionDictionary[link.target], + })); - let distances = await findDistancesForAllElements({ nodes, links }) - rows = nodes.map((element, i) => ({ - id: numToAlphabeticalString(element.position), - position: element.position, - name: element.name, - distances: distances[i] - })) - console.log(rows) - setTableRows(rows) - } + let distances = await findDistancesForAllElements({ nodes, links }); + rows = nodes.map((element, i) => ({ + id: numToAlphabeticalString(element.position), + position: element.position, + name: element.name, + distances: distances[i], + })); + console.log(rows); + setTableRows(rows); + } } export function CreateTable({ tableRows }) { - /* This function receives a list of rows, and displays them nicely. */ - function abridgeArrayAndDisplay(array) { - let newArray - let formatForDisplay - 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)) - } - let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "") - return result + /* This function receives a list of rows, and displays them nicely. */ + function abridgeArrayAndDisplay(array) { + let newArray; + let formatForDisplay; + 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)); } - return ( -
- - - - - - - - - - - - - - - - {tableRows.map(row => - - - - - - - - - - - )} - -
Id   Position   Element    Possible relative values    Average relative value
{row.id}   {row.position}   {row.name}   {abridgeArrayAndDisplay(row.distances)}   {formatLargeOrSmall(avg(row.distances))}
-
- ) - + let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, ""); + return result; + } + return ( +
+ + + + + + + + + + + + + + + + {tableRows.map((row) => ( + + + + + + + + + + + + ))} + +
Id   Position   Element    Possible relative values    Average relative value
{row.id}   {row.position}   {row.name}   {abridgeArrayAndDisplay(row.distances)}    + {formatLargeOrSmall(geomMean(row.distances))} +
+
+ ); } /* Testing */ @@ -300,4 +343,5 @@ console.log(JSON.stringify(paths, null, 2)) /* let distances = findDistance({sourceElementId:2, targetElementId:4, links, nodes}) console.log(distances) -*/ \ No newline at end of file +*/ + diff --git a/lib/labeledGraph.js b/lib/labeledGraph.js index ec42813..4d12988 100644 --- a/lib/labeledGraph.js +++ b/lib/labeledGraph.js @@ -1,145 +1,171 @@ import React, { useState, useEffect } from "react"; -import * as d3 from 'd3'; -import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "./utils.js" +import * as d3 from "d3"; +import { + toLocale, + truncateValueForDisplay, + numToAlphabeticalString, + formatLargeOrSmall, +} from "./utils.js"; let getlength = (number) => number.toString().length; export function removeOldSvg() { - d3.select("#graph").select("svg").remove(); + d3.select("#graph").select("svg").remove(); } function drawGraphInner({ nodes, links }) { + // List of node ids for convenience + var nodeids = nodes.map((node) => node.id); + var positionById = {}; + nodeids.forEach((nodeid, i) => (positionById[nodeid] = i)); + console.log("NodeIds/positionById"); + console.log(nodeids); + console.log(positionById); - // List of node ids for convenience - var nodeids = nodes.map(node => node.id) - var positionById = {} - nodeids.forEach((nodeid, i) => positionById[nodeid] = i) - console.log("NodeIds/positionById") - console.log(nodeids) - console.log(positionById) + // Calculate the dimensions + // let margin = { top: 0, right: 30, bottom: 20, left: 30 }; + // let width = 900 - margin.left - margin.right; - // Calculate the dimensions - // let margin = { top: 0, right: 30, bottom: 20, left: 30 }; - // let width = 900 - margin.left - margin.right; - - let initialWindowWidth = window.innerWidth - let margin = { top: 0, right: 10, bottom: 30, left: 10 }; - let width = initialWindowWidth*0.7 - margin.left - margin.right; - - var x = d3.scalePoint() - .range([0, width]) - .domain(nodeids) + let initialWindowWidth = window.innerWidth; + let margin = { top: 0, right: 10, bottom: 30, left: 10 }; + let width = initialWindowWidth * 0.8 - margin.left - margin.right; - let heights = links.map(link => { - let start = x(positionById[link.source]) - let end = x(positionById[link.target]) - return Math.abs(start - end) / 2 + 70 // Magic constant. + var x = d3.scalePoint().range([0, width]).domain(nodeids); + + let heights = links.map((link) => { + let start = x(positionById[link.source]); + let end = x(positionById[link.target]); + return Math.abs(start - end) / 2 + 70; // Magic constant. + }); + console.log(heights); + let maxheight = Math.max(...heights); + let height = maxheight - margin.top - margin.bottom; + console.log(`height: ${height}`); + + // Build the d3 graph + + removeOldSvg(); + var svg = d3 + .select("#graph") + .append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + // A linear scale to position the nodes on the X axis + + // Add the circle for the nodes + svg + .selectAll("mynodes") + .data(nodes) + .enter() + .append("circle") + .attr("cx", function (d) { + return x(d.id); }) - console.log(heights) - let maxheight = Math.max(...heights) - let height = maxheight - margin.top - margin.bottom; - console.log(`height: ${height}`) + .attr("cy", height - 30) + .attr("r", 8) + .style("fill", "#69b3a2"); - // Build the d3 graph + // And give them a label + svg + .selectAll("mylabels") + .data(nodes) + .enter() + .append("text") + .attr("x", function (d) { + return x(d.id); + }) + .attr("y", height - 10) + .text(function (d) { + return numToAlphabeticalString(d.position); + }) + .style("text-anchor", "middle"); - removeOldSvg() - var svg = d3.select("#graph") - .append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + // Add the links + svg + .selectAll("mylinks") + .data(links) + .enter() + .append("path") + .attr("d", function (d) { + let start = x(d.source); + // X position of start node on the X axis + let end = x(d.target); + // X position of end node + return ( + [ + "M", + start, + height - 30, + // the arc starts at the coordinate x=start, y=height-30 (where the starting node is) + "A", + // This means we're gonna build an elliptical arc + (start - end) / 2, + ",", + // Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance + (start - end) / 2, + 0, + 0, + ",", + start < end ? 1 : 0, + end, + ",", + height - 30, + ] + // We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down. + .join(" ") + ); + }) + .style("fill", "none") + .attr("stroke", "black"); - // A linear scale to position the nodes on the X axis - - - // Add the circle for the nodes - svg - .selectAll("mynodes") - .data(nodes) - .enter() - .append("circle") - .attr("cx", function (d) { return (x(d.id)) }) - .attr("cy", height - 30) - .attr("r", 8) - .style("fill", "#69b3a2") - - // And give them a label - svg - .selectAll("mylabels") - .data(nodes) - .enter() - .append("text") - .attr("x", function (d) { return (x(d.id)) }) - .attr("y", height - 10) - .text(function (d) { return numToAlphabeticalString(d.position) }) - .style("text-anchor", "middle") - - // Add the links - svg - .selectAll('mylinks') - .data(links) - .enter() - .append('path') - .attr('d', function (d) { - let start = x(d.source) - // X position of start node on the X axis - let end = x(d.target) - // X position of end node - return ['M', - start, - height - 30, - // the arc starts at the coordinate x=start, y=height-30 (where the starting node is) - 'A', - // This means we're gonna build an elliptical arc - (start - end) / 2, ',', - // Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance - (start - end) / 2, 0, 0, ',', - start < end ? 1 : 0, end, ',', height - 30] - // We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down. - .join(' '); - }) - .style("fill", "none") - .attr("stroke", "black") - - // labels for links - svg - .selectAll('mylinks') - .data(links) - .enter() - .append("text") - .attr("x", function (d) { - let start = x(d.source) - // X position of start node on the X axis - let end = x(d.target) - // X position of end node - return start + (end - start) / 2 //-4*getlength(d.distance) - }) - .attr("y", function (d) { - let start = x(d.source) - // X position of start node on the X axis - let end = x(d.target) - // X position of end node - return height - 32 - (Math.abs(start - end) / 2)//height-30 - }) - .text(function (d) { - return formatLargeOrSmall(Number(d.distance)) - // return (truncateValueForDisplay(Number(d.distance))) - //return(Number(d.distance).toPrecision(2).toString()) - }) - .style("text-anchor", "middle") + // labels for links + svg + .selectAll("mylinks") + .data(links) + .enter() + .append("text") + .attr("x", function (d) { + let start = x(d.source); + // X position of start node on the X axis + let end = x(d.target); + // X position of end node + return start + (end - start) / 2; //-4*getlength(d.distance) + }) + .attr("y", function (d) { + let start = x(d.source); + // X position of start node on the X axis + let end = x(d.target); + // X position of end node + return height - 32 - Math.abs(start - end) / 2; //height-30 + }) + .text(function (d) { + return formatLargeOrSmall(Number(d.distance)); + // return (truncateValueForDisplay(Number(d.distance))) + //return(Number(d.distance).toPrecision(2).toString()) + }) + .style("text-anchor", "middle"); } -export function DrawGraph({ isListOrdered, orderedList, listOfElements, links }) { - if (isListOrdered) { - let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) - drawGraphInner({ nodes, links }); - } - return ( -
-
-
-
- ); +export function DrawGraph({ + isListOrdered, + orderedList, + listOfElements, + links, +}) { + if (isListOrdered) { + let nodes = orderedList.map((id, pos) => ({ + ...listOfElements[id], + position: pos, + })); + drawGraphInner({ nodes, links }); + } + return ( +
+
+
+ ); +} -} \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 8a9957b..b2647bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,78 +1,94 @@ -import crypto from "crypto" +import crypto from "crypto"; -export const hashString = (string) => crypto.createHash('md5').update(string).digest('hex'); -const id = x => x -export const transformSliderValueToActualValue = id -export const transformSliderValueToPracticalValue = id +export const hashString = (string) => + crypto.createHash("md5").update(string).digest("hex"); +const id = (x) => x; +export const transformSliderValueToActualValue = id; +export const transformSliderValueToPracticalValue = id; -export const _transformSliderValueToActualValue = value => 10 ** value //>= 2 ? Math.round(10 ** value) : Math.round(10 * 10 ** value) / 10 -export const toLocale = x => Number(x).toLocaleString() -export const truncateValueForDisplay = value => { - if (value > 10) { - return Number(Math.round(value).toPrecision(2)) - } else if (value > 1) { - return Math.round(value * 10) / 10 - } else if (value < 1) { - - } -} -export const _transformSliderValueToPracticalValue = value => truncateValueForDisplay(transformSliderValueToActualValue(value)) +export const _transformSliderValueToActualValue = (value) => 10 ** value; //>= 2 ? Math.round(10 ** value) : Math.round(10 * 10 ** value) / 10 +export const toLocale = (x) => Number(x).toLocaleString(); +export const truncateValueForDisplay = (value) => { + if (value > 10) { + return Number(Math.round(value).toPrecision(2)); + } else if (value > 1) { + return Math.round(value * 10) / 10; + } else if (value < 1) { + } +}; +export const _transformSliderValueToPracticalValue = (value) => + truncateValueForDisplay(transformSliderValueToActualValue(value)); export function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } export function numToAlphabeticalString(num) { - // https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript/45787487 - num = num + 1 - var s = '', t; - while (num > 0) { - t = (num - 1) % 26; - s = String.fromCharCode(65 + t) + s; - num = (num - t) / 26 | 0; - } - return `#${s}` || undefined; + // https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript/45787487 + num = num + 1; + var s = "", + t; + while (num > 0) { + t = (num - 1) % 26; + s = String.fromCharCode(65 + t) + s; + num = ((num - t) / 26) | 0; + } + return `#${s}` || undefined; } export function formatLargeOrSmall(num) { - if (num > 1) { - return toLocale(truncateValueForDisplay(num)) - } else if (num > 0) { - return num.toFixed(-Math.floor(Math.log(num) / Math.log(10)) + 1); - } else if (num < -1) { - return num.toFixed(-Math.floor(Math.log(-num) / Math.log(10)) + 1); - } else { - return toLocale(num)//return "~0" - - } + if (num > 1) { + return toLocale(truncateValueForDisplay(num)); + } else if (num > 0) { + return num.toFixed(-Math.floor(Math.log(num) / Math.log(10)) + 1); + } else if (num < -1) { + return num.toFixed(-Math.floor(Math.log(-num) / Math.log(10)) + 1); + } else { + return toLocale(num); //return "~0" + } } -const firstFewMaxMergeSortSequence = [0, 0, 1, 3, 5, 8, 11, 14, 17, 21, 25, 29, 33, 37, 41, 45, 49, 54, 59, 64, 69, 74, 79, 84, 89, 94, 99, 104, 109, 114, 119, 124, 129, 135, 141, 147, 153, 159, 165, 171, 177, 183, 189, 195, 201, 207, 213, 219, 225, 231, 237, 243, 249, 255, 261, 267, 273, 279, 285] +const firstFewMaxMergeSortSequence = [ + 0, 0, 1, 3, 5, 8, 11, 14, 17, 21, 25, 29, 33, 37, 41, 45, 49, 54, 59, 64, 69, + 74, 79, 84, 89, 94, 99, 104, 109, 114, 119, 124, 129, 135, 141, 147, 153, 159, + 165, 171, 177, 183, 189, 195, 201, 207, 213, 219, 225, 231, 237, 243, 249, + 255, 261, 267, 273, 279, 285, +]; export function maxMergeSortSteps(n) { - if (n < firstFewMaxMergeSortSequence.length) { - return firstFewMaxMergeSortSequence[n] - } else { - return maxMergeSortSteps(Math.floor(n / 2)) + maxMergeSortSteps(Math.ceil(n / 2)) + n - 1 - } + if (n < firstFewMaxMergeSortSequence.length) { + return firstFewMaxMergeSortSequence[n]; + } else { + return ( + maxMergeSortSteps(Math.floor(n / 2)) + + maxMergeSortSteps(Math.ceil(n / 2)) + + n - + 1 + ); + } } export function expectedNumMergeSortSteps(n) { - // https://cs.stackexchange.com/questions/82862/expected-number-of-comparisons-in-a-merge-step - // n-2 for each step, so (n-2) + (n-2)/2 + (n-2)/4 + ... - // ~ 2*(n-2) -1 = 2*n - 3 - if (n == 0) { - return 0 - } else if (n == 1) { - return 0 - } else if (n == 2) { - return 1 - } else if (n == 3) { - return 2 - } else { - return Math.ceil((n ** 2) / (n + 2)) + expectedNumMergeSortSteps(Math.ceil(n / 2)) - } + // https://cs.stackexchange.com/questions/82862/expected-number-of-comparisons-in-a-merge-step + // n-2 for each step, so (n-2) + (n-2)/2 + (n-2)/4 + ... + // ~ 2*(n-2) -1 = 2*n - 3 + if (n == 0) { + return 0; + } else if (n == 1) { + return 0; + } else if (n == 2) { + return 1; + } else if (n == 3) { + return 2; + } else { + return ( + Math.ceil(n ** 2 / (n + 2)) + expectedNumMergeSortSteps(Math.ceil(n / 2)) + ); + } } -export const avg = arr => arr.reduce((a, b) => (a + b), 0) / arr.length +export const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length; -export const increasingList = (n) => Array.from(Array(n).keys()) +export const geomMean = (arr) => + arr.reduce((a, b) => a * b, 1) ^ (1 / arr.length); + +export const increasingList = (n) => Array.from(Array(n).keys());