javascriptarraysgaussiansplicebatching

1D -> 2D Array W/Normal Curve Sub-Array Lengths


I am trying to break a 1D array into a 2D array where the sub-arrays are of varying lengths. This variance should follow the gaussian curve [or a mound shape]. So, say the 2D array variable we make is named gaussianCurve. The array within gaussianCurve[0] & gaussianCurve[n] would be of length 1, and gaussianCurve[n/2] would be a maximum provided by a parameter "maxArrayLength". This forces the number of gaussianCurve indexes to become variable.

Say I have the following psuedo-code:

function (oneDimentionalArray, maxArrayLength) {
// oneDimentionalArray is ["A","B","C","D","E","F","G","H","I","J","K"]
// maxArrayLength is 5
// Currently working like this (i.e. "batches"):
// return [["A","B","C","D","E"],["F","G","H","I","J"],["K"]]
// would LIKE it to work like this
    gaussianCurve = []
    gaussianCurve.push(["A"])
    gaussianCurve.push(["B", "C"])
    gaussianCurve.push(["D", "E", "F", "G", "H"])
    gaussianCurve.push(["I", "J"])
    gaussianCurve.push(["K"])

    return  gaussianCurve
}

Why would I want such a thing? Progress bars.

  1. They don’t show I am making progress immediately
    1. This is because the first job must complete before the bar can move
  2. They slow down at 95%+ and sometimes even stick at 100%
    1. Just annoying

Any suggestions are welcome. I am just not seeing the answer in my minds eye.

EDIT: I feel it was worded poorly, so I am rewording it.

...gaussianCurve[0].length & gaussianCurve[gaussianCurve.length - 1].length would be 1, and gaussianCurve[gaussianCurve.length/2].length would be up to "maxArrayLength".

INPUT:

function gaussianRefactor(["A","B","C","D","E","F","G","H","I","J","K"], 1)
function gaussianRefactor(["A","B","C","D","E","F","G","H","I","J","K"], 2)
function gaussianRefactor(["A","B","C","D","E","F","G","H","I","J","K"], 4)
function gaussianRefactor(["A","B","C","D","E","F","G","H","I","J","K"], 8)
function gaussianRefactor(["A","B","C","D","E","F","G","H","I","J","K"], 16)

OUTPUT:

[["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"]]
[["A"],["B","C"],["D","E"],["F","G"],["H","I"],["J"],["K"]]
[["A"],["B","C","D"],["E","F","G","H"],["I","J","K"]]
[["A"],["B","C","D","E","F","G","H","I"],["J","K"]]
[["A","B","C","D","E","F","G","H","I","J","K"]]

No inner array may exceed the length of maxArrayLength


Solution

  • This was more inline with what I was thinking. I greatly dislike the way I am finding sigma. I know I should just reorder the formula to calculate it, but I have yet to get that to work. Anyway, here is the, "answer" though it fails for the smaller arrays I provided as examples in the question, it successfully does what I needed to do. If anyone has improvements they'd like to add, just let me know.

    var gaussianRefactor = function(srcOneDimentionalArray, srcMaxArrayLength) {
      var finalArray = [];
      if (srcOneDimentionalArray.length <= srcMaxArrayLength) {
        finalArray.push(srcOneDimentionalArray);
        return finalArray;
      }
      if (srcMaxArrayLength === 1) {
      for(var lengthOne = 0; lengthOne < srcOneDimentionalArray.length; lengthOne++)
        finalArray.push([srcOneDimentionalArray[lengthOne]]);
        return finalArray;
      }
      var maxArrayLength = srcMaxArrayLength;
      var oneDimentionalArray = srcOneDimentionalArray.slice(0);
      for (var x = srcMaxArrayLength; x > 1 && maxArrayLength / oneDimentionalArray.length > 0.3333; x--) {
        maxArrayLength--;
      }
      var standardChunkSize = srcOneDimentionalArray.length / maxArrayLength;
      var predictedSize = (3 * Math.floor(standardChunkSize)) % 2 === 0 ? 3 * Math.floor(standardChunkSize) + 1 : 3 * Math.floor(standardChunkSize);
      var predictedSizeCenter = Math.ceil(predictedSize / 2);
      var sigma = 0.2034185 * Math.pow(standardChunkSize, 1.963449);
      var multiplicand = 1 / (Math.sqrt(sigma) * Math.sqrt(2 * Math.PI));
      var centerGauss = maxArrayLength / multiplicand;
      var mu = 0;
      var delta;
      var fraction;
      var exponent;
      var full;
      var subArrayLength;
      var subArray;
      var notWideEnough = true;
      var maxElements;
      var maxAttempts = Math.max(Math.ceil(sigma), 100);
      var currentAttempts = 0;
      while (notWideEnough && currentAttempts < maxAttempts) {
        maxElements = 0;
        for (var j = 0; j < predictedSize; j++) {
          delta = (j - predictedSizeCenter) - mu;
          fraction = delta / Math.sqrt(sigma);
          exponent = -0.5 * Math.pow(fraction, 2);
          full = multiplicand * Math.exp(exponent);
          subArrayLength = Math.floor(full * centerGauss);
          maxElements += subArrayLength;
        }
        if (maxElements >= srcOneDimentionalArray.length) {
          notWideEnough = false;
        } else {
          sigma = sigma + sigma * 0.05;
        }
        currentAttempts++;
      }
      if (currentAttempts === maxAttempts) {
        return false;
      }
    
      for (var i = 0; i < predictedSize; i++) {
        delta = (i - predictedSizeCenter) - mu;
        fraction = delta / Math.sqrt(sigma);
        exponent = -0.5 * Math.pow(fraction, 2);
        full = multiplicand * Math.exp(exponent);
        subArrayLength = Math.floor(full * centerGauss);
        if (subArrayLength < 1 || oneDimentionalArray.length < 1) {
          continue;
        }
        subArray = oneDimentionalArray.slice(0, subArrayLength);
        oneDimentionalArray = oneDimentionalArray.slice(subArrayLength, oneDimentionalArray.length);
        finalArray.push(subArray);
      }
      return finalArray;
    }
    

    INPUT

    gaussianRefactor(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"], 1)
    gaussianRefactor(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"], 2)
    gaussianRefactor(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"], 4)
    gaussianRefactor(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"], 8)
    gaussianRefactor(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"], 16)
    

    OUTPUT

    [["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"]]
    [["A"],["B"],["C"],["D"],["E"],["F","G"],["H"],["I"],["J"],["K"]]
    [["A"],["B"],["C","D"],["E","F","G"],["H","I"],["J"],["K"]]
    [["A"],["B"],["C","D"],["E","F","G"],["H","I"],["J"],["K"]]
    [["A","B","C","D","E","F","G","H","I","J","K"]]
    

    -- EDIT 2021 -- https://jsfiddle.net/brightmatter_og/xzfwjeaq/ I found the answer I was seeking. I will put it here if anyone is interested.

    function removeMeFromTheList(removeMe, theList) {
      for (let i = 0; i < theList.length; i++) {
        if (theList[i] === removeMe) {
          theList.splice(i, 1);
          return;
        }
      }
    }
    
    function makeListOfGaussianLists(chunkWeights, objects) {
      const listOfLists = [];
      const chunkWeightsSize = Object.keys(chunkWeights).length;
      if (chunkWeightsSize < 1 || objects.length < chunkWeightsSize) {
        console.info("chunkWeights.length:" + chunkWeightsSize + " - objects.length:" + objects.length);
        return null;
      }
      const elementCount = objects.length / chunkWeightsSize;
      let modifiedElementCount = null;
      for (let i = 0; i < chunkWeightsSize; i++) {
        modifiedElementCount = parseInt(chunkWeights[i] * elementCount);
        let innerList = [];
        for (let j = modifiedElementCount; j > 0; j--) {
          const obj = objects[0];
          if (obj == null) {
            break;
          }
          innerList.push(obj);
          removeMeFromTheList(obj, objects);
        }
        listOfLists.push(innerList);
      }
      if (objects.length > 0) {
        do {
          for (let i = 0; i < listOfLists.length; i++) {
            const obj = objects[0];
            if (obj == null) {
              break;
            }
            listOfLists[i].push(obj);
            removeMeFromTheList(obj, objects);
          }
        } while (objects.length > 0);
      }
      return listOfLists;
    }
    
    function subListWeightBuilder(partitionCount) {
      const gauss = [];
      const population = 250;
      const chunkWeights = {};
      if (partitionCount > population) {
        return chunkWeights;
      }
      for (let i = 0; i < population * 2; i++) {
        gauss.push(randomGaussian());
      }
      gauss.sort(function(a, b){return a - b});
      const partitionWidth = gauss.length / partitionCount;
      let currentCount = 0;
      let chunkWeightsIndex = 0;
      let pastTheFirst = false;
      for (let j = 0; j < gauss.length; j++) {
        if (currentCount < partitionWidth) {
          chunkWeights[chunkWeightsIndex] = 1 / (Math.abs(gauss[j]) + 0.66);
        } else {
          chunkWeightsIndex++;
          currentCount = 0;
        }
        currentCount++;
      }
      let offset = 0;
      for (let key in chunkWeights) {
        offset += chunkWeights[key];
      }
      offset /= partitionCount;
      offset = (1 - offset) + Number.MIN_VALUE;
      for (let key in chunkWeights) {
        let value = chunkWeights[key];
        chunkWeights[key] = value + offset;
      }
      return chunkWeights;
    }
    
    function randomGaussian() {
      let r = 0;
      const iteration = 6;
      for (let i = iteration; i > 0; i--) {
        r += Math.random();
      }
      return r / iteration - 0.5;
    }
    
    function pressButton() {
      let partitionCount = 23;
      console.info(makeListOfGaussianLists(subListWeightBuilder(partitionCount), makeRandomString(333).split("")));
      partitionCount = 41;
      console.info(makeListOfGaussianLists(subListWeightBuilder(partitionCount), makeRandomString(666).split("")));
      partitionCount = 67;
      console.info(makeListOfGaussianLists(subListWeightBuilder(partitionCount), makeRandomString(1000).split("")));
      partitionCount = 41;
      console.info(makeListOfGaussianLists(subListWeightBuilder(partitionCount), makeRandomString(1333).split("")));
      partitionCount = 23;
      console.info(makeListOfGaussianLists(subListWeightBuilder(partitionCount), makeRandomString(1666).split("")));
    }
    
    function makeRandomString(size) {
      const loops = parseInt(size / 11);
      let theResult = "";
      for(let i = loops; i > 0; i--) {
        theResult += Array(11 + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, 11);
      }
      return theResult += Array((size - 11 * loops) + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, (size - 11 * loops))
    }