From bd5bdb9cd009108856fbf339122f89974a368b3d Mon Sep 17 00:00:00 2001 From: NunoSempere Date: Sun, 30 Jan 2022 10:26:05 -0500 Subject: [PATCH] fix: Pander to human biases. The expected number of steps is less than the true number half the time. This confuses users. Also some formatting. --- "lib/\\" | 583 ++++++++++++++++++++++++++++++++++++++++++ lib/comparisonView.js | 474 ++++++++++++++++++++-------------- lib/labeledGraph.js | 6 +- lib/utils.js | 3 + 4 files changed, 879 insertions(+), 187 deletions(-) create mode 100644 "lib/\\" diff --git "a/lib/\\" "b/lib/\\" new file mode 100644 index 0000000..11c5eb8 --- /dev/null +++ "b/lib/\\" @@ -0,0 +1,583 @@ +/* Imports */ +import Head from "next/head"; +import React, { useState } from "react"; +import { DrawGraph, removeOldSvg } from "./labeledGraph"; +import { SubmitSliderButton } from "./slider"; +import { DisplayElement } from "./displayElement"; +import { DisplayAsMarkdown } from "./displayAsMarkdown"; +import { CreateTable, buildRows } from "./findPaths"; +import { DataSetChanger } from "./datasetChanger"; +import { ComparisonsChanger } from "./comparisonsChanger"; +import { pushToMongo } from "./pushToMongo.js"; +import { + increasingList, + maxMergeSortSteps, + expectedNumMergeSortSteps, + conservativeNumMergeSortSteps, +} from "./utils.js"; + +/* DEFINTIONS */ +const DEFAULT_COMPARE = () => 1; // 1/Math.random() - 1 + +/* Misc. helpers */ +let buildLinks = (quantitativeComparisons) => + quantitativeComparisons.map(([element1, element2, distance, reasoning]) => ({ + source: element1, + target: element2, + distance: distance, + reasoning: reasoning, + })); + +Array.prototype.containsArray = function (val) { + var hash = {}; + for (var i = 0; i < this.length; i++) { + hash[this[i]] = i; + } + return hash.hasOwnProperty(val); +}; + +let checkIfListIsOrdered = (arr, binaryComparisons) => { + let l = arr.length; + let isOrdered = true; + for (let i = 0; i < l - 1; i++) { + isOrdered = + isOrdered && binaryComparisons.containsArray([arr[i], arr[i + 1]]); + } + return isOrdered; +}; + +let nicelyFormatLinks = (quantitativeComparisons, listOfElements) => + quantitativeComparisons.map(([element1, element2, distance, reasoning]) => ({ + source: listOfElements[element1].name, + target: listOfElements[element2].name, + distance: distance, + reasoning: reasoning, + })); + +// Main react component +export default function ComparisonView({ listOfElementsForView }) { + /* State */ + // initial state values + let initialListOfElements = listOfElementsForView.map((element, i) => ({ + ...element, + id: i, + })); + let initialPosList = increasingList(listOfElementsForView.length); // [0,1,2,3,4] + let initialComparePair = [ + initialPosList[initialPosList.length - 2], + initialPosList[initialPosList.length - 1], + ]; + let initialSliderValue = 1; + let initialReasoning = ""; + let initialBinaryComparisons = []; + let initialQuantitativeComparisons = []; + let initialIsListOdered = false; + let initialOrderedList = []; + let initialShowAdvancedOptions = false; + let initialShowComparisons = false; + let initialShowLoadComparisons = false; + let initialShowChangeDataSet = false; + let initialNumSteps = 0; + let initialMaxSteps = maxMergeSortSteps(listOfElementsForView.length); + let initialExpectedSteps = expectedNumMergeSortSteps( + listOfElementsForView.length + ); + let initialTableRows = []; + let initialDontPushSubmitButtonAnyMore = false; + + // state variables and functions + const [listOfElements, setListOfElements] = useState(initialListOfElements); + const [posList, setPosList] = useState(initialPosList); + const [toComparePair, setToComparePair] = useState(initialComparePair); + const [sliderValue, setSliderValue] = useState(initialSliderValue); + const [reasoning, setReasoning] = useState(initialReasoning); + const [binaryComparisons, setBinaryComparisons] = useState( + initialBinaryComparisons + ); + const [quantitativeComparisons, setQuantitativeComparisons] = useState( + initialQuantitativeComparisons + ); // More expressive, but more laborious to search through. For the ordering step, I only manipulate the binaryComparisons. + + const [isListOrdered, setIsListOrdered] = useState(initialIsListOdered); + const [orderedList, setOrderedList] = useState(initialOrderedList); + const [dontPushSubmitButtonAnyMore, setDontPushSubmitButtonAnyMore] = + useState(initialDontPushSubmitButtonAnyMore); + + let [showAdvancedOptions, changeShowAdvanceOptions] = useState( + initialShowAdvancedOptions + ); + let [showComparisons, changeShowComparisons] = useState( + initialShowComparisons + ); + let [showLoadComparisons, changeShowLoadComparisons] = useState( + initialShowLoadComparisons + ); + let [showChangeDataSet, changeshowChangeDataSet] = useState( + initialShowChangeDataSet + ); + let [numSteps, changeNumSteps] = useState(initialNumSteps); + let [maxSteps, changeMaxSteps] = useState(initialMaxSteps); + let [expectedSteps, changeExpectedSteps] = useState(initialExpectedSteps); + let [tableRows, setTableRows] = useState(initialTableRows); + + /* Convenience utils: restart + changeDataSet */ + let restart = ( + posList, + initialBinaryComparisons2, + initialQuantitativeComparisons2 + ) => { + //({posList, initialBinaryComparisons2, initialQuantitativeComparisons2}) => { + setToComparePair([ + posList[posList.length - 2], + posList[posList.length - 1], + ]); + setSliderValue(initialSliderValue); + setBinaryComparisons(initialBinaryComparisons2 || initialBinaryComparisons); + setQuantitativeComparisons( + initialQuantitativeComparisons2 || initialQuantitativeComparisons + ); + setIsListOrdered(initialIsListOdered); + setOrderedList(initialOrderedList); + changeNumSteps(initialNumSteps); + removeOldSvg(); + setTableRows(initialTableRows); + setDontPushSubmitButtonAnyMore(initialDontPushSubmitButtonAnyMore); + }; + + let changeDataSet = (listOfElementsNew) => { + listOfElementsNew = listOfElementsNew.map((element, i) => ({ + ...element, + id: i, + })); + let newPosList = increasingList(listOfElementsNew.length); + let newListLength = listOfElementsNew.length; + + setListOfElements(listOfElementsNew); + setPosList(increasingList(listOfElementsNew.length)); + setToComparePair([ + newPosList[newPosList.length - 2], + newPosList[newPosList.length - 1], + ]); + + changeExpectedSteps(expectedNumMergeSortSteps(newListLength)); + changeMaxSteps(maxMergeSortSteps(newListLength)); + + restart(newPosList); + // restart({posList: newPosList}) + }; + + let changeComparisons = async (links) => { + let quantitativeComparisons2 = []; + let binaryComparisons2 = []; + links.shift(); + for (let link of links) { + let { source, target, distance, reasoning } = link; + let searchByName = (name, candidate) => candidate.name == name; + let testForSource = (candidate) => searchByName(source, candidate); + let testForTarget = (candidate) => searchByName(target, candidate); + let element1 = listOfElements.findIndex(testForSource); + let element2 = listOfElements.findIndex(testForTarget); + if (element1 == -1 || element2 == -1) { + console.log("link", link); + console.log(source); + console.log(target); + throw new Error("Comparisons include unknown elements, please retry"); + } + quantitativeComparisons2.push([element1, element2, distance, reasoning]); + binaryComparisons2.push([element1, element2]); + } + // return ({quantitativeComparisons: quantitativeComparisons2, binaryComparisons: binaryComparisons2}) + //restart({posList, initialBinaryComparisons2=initialBinaryComparisons, initialQuantitativeComparisons2=initialQuantitativeComparisons}) + // restart(posList, binaryComparisons2, quantitativeComparisons2) + setQuantitativeComparisons(quantitativeComparisons2); + setBinaryComparisons(binaryComparisons2); + }; + + // Manipulations + let compareTwoElements = (newBinaryComparisons, element1, element2) => { + let element1Greater = newBinaryComparisons.containsArray([ + element1, + element2, + ]); + let element2Greater = newBinaryComparisons.containsArray([ + element2, + element1, + ]); + if (element1Greater || element2Greater) { + return element1Greater && !element2Greater; + } else { + setToComparePair([element1, element2]); + //console.log(`No comparison found between ${element1} and ${element2}`) + //console.log(`Comparisons:`) + //console.log(JSON.stringify(newBinaryComparisons, null, 4)); + return "No comparison found"; + } + }; + + function merge(newBinaryComparisons, left, right) { + let sortedArr = []; // the sorted elements will go here + + while (left.length && right.length) { + // insert the biggest element to the sortedArr + let comparison = compareTwoElements( + newBinaryComparisons, + left[0], + right[0] + ); + if (comparison == "No comparison found") { + return "No comparison found; unable to proceed"; + } else if (comparison) { + // left[0] > right[0] + sortedArr.push(left.shift()); + } else { + sortedArr.push(right.shift()); + } + } + + // use spread operator and create a new array, combining the three arrays + return [...sortedArr, ...left, ...right]; // if they don't have the same size, the remaining ones will be greater than the ones before + } + + function mergeSort({ array, comparisons }) { + if (array == "No comparison found; unable to proceed") { + return "No comparison found; unable to proceed"; + } + const half = array.length / 2; + + // the base case is array length <=1 + if (array.length <= 1) { + return array; + } + + const left = array.slice(0, half); // the first half of the array + const right = array.slice(half, array.length); // Note that splice is destructive. + let orderedFirstHalf = mergeSort({ array: left, comparisons }); + let orderedSecondHalf = mergeSort({ array: right, comparisons }); + if ( + orderedFirstHalf != "No comparison found; unable to proceed" && + orderedSecondHalf != "No comparison found; unable to proceed" + ) { + let result = merge(comparisons, orderedFirstHalf, orderedSecondHalf); + return result; + } else { + return "No comparison found; unable to proceed"; + } + } + + let nextStepSimple = (posList, binaryComparisons, element1, element2) => { + //console.log("Binary comparisons: ") + //console.log(JSON.stringify(binaryComparisons, null, 4)); + + let newComparison = [element1, element2]; // [element1, element2] + let newBinaryComparisons = [...binaryComparisons, newComparison]; + //console.log("New binaryComparisons: ") + //console.log(JSON.stringify(newBinaryComparisons, null, 4)); + setBinaryComparisons(newBinaryComparisons); + + let result = mergeSort({ + array: posList, + comparisons: newBinaryComparisons, + }); + //console.log(result) + if ( + result != "No comparison found; unable to proceed" && + checkIfListIsOrdered(result, newBinaryComparisons) + ) { + // console.log(`isListOrdered: ${isListOrdered}`) + console.log("poslist@nextStepSimple"); + console.log(posList); + console.log("result@nextStepSimple"); + console.log(result); + + return [true, result]; + } else { + return [false, result]; + } + }; + + let nextStepSlider = async ({ + posList, + binaryComparisons, + sliderValue, + reasoning, + element1, + element2, + }) => { + if (!dontPushSubmitButtonAnyMore) { + if (sliderValue < 1 && sliderValue > 0) { + sliderValue = 1 / sliderValue; + [element1, element2] = [element2, element1]; + } + console.log(`posList@nextStepSlider:`); + console.log(posList); + let [successStatus, result] = nextStepSimple( + posList, + binaryComparisons, + element1, + element2 + ); + + let newQuantitativeComparison = [ + element1, + element2, + sliderValue, + reasoning, + ]; + let newQuantitativeComparisons = [ + ...quantitativeComparisons, + newQuantitativeComparison, + ]; + setQuantitativeComparisons(newQuantitativeComparisons); + + setSliderValue(DEFAULT_COMPARE()); + setReasoning(""); + changeNumSteps(numSteps + 1); + if (successStatus) { + setDontPushSubmitButtonAnyMore(true); + + let jsObject = nicelyFormatLinks( + quantitativeComparisons, + listOfElements + ); + await pushToMongo(jsObject); + console.log(jsObject); + + alert( + "Comparisons completed. Background work might take a while, or straight-out fail" + ); + setIsListOrdered(true); + setOrderedList(result); + + await buildRows({ + isListOrdered: true, + orderedList: result, + listOfElements, + links: buildLinks(newQuantitativeComparisons), + rows: tableRows, + setTableRows, + }); + /* + setTimeout(async () => { + // Make sure to do this after the + + }, 100); + */ + } + } + }; + + // Html + return ( +
+ {/* Webpage name & favicon */} +
+ + Utility Function Extractor + + +
+
+ {/* Heading */} +

Utility Function Extractor

+ {/* Approximate progress indicator */} + +

{`${numSteps} out of ~${expectedSteps} (max ${maxSteps}) comparisons`}

+ + {/* Comparison section */} +
+
+ {/* Element 1 */} +
+
+ +
+
+ + {/* Comparison actuator (text, previously slider) */} +
+
+
+ +
+ + +
+
+ + {/* Element 2 */} +
+
+ +
+
+
+
+ +