Search code examples
javascriptobject

Creating millions of Objects in Javascript


Let me be the first to say that this isn't something I normally do, but out of curiousity, I'll see if anyone has a good idea on how to handle a problem like this.

The application I am working on is a simulated example of the game Let's make a Deal featuring the Monty Hall problem.

I won't go into details about my implementation, but it more or less allows a user to enter a number of how many games they want to simulate, and then if an option is toggled off, the player of those x games won't switch their choice, while if it is toggled on, they will switch their choice every single instance of the game.

My object generator looks like this:

const game = function(){
    this[0] = null;
    this[1] = null;
    this[2] = null;
    this.pick = Math.floor(Math.random() * 3);
    this.correctpick = Math.floor(Math.random() * 3);
    this[this.correctpick] = 1;
    for (let i=0; i<3; i++){
        if ((this[i] !== 1) && (i !== this.pick)){
            this.eliminated = i;
            break;
        }
    }
}

const games = arg => {
    let ret = [];
    for(let i=0; i<arg; i++){
        ret.push(new game);
    }
    return ret;
}

This structure generates an array which i stringify later that looks like this:

[
  {
    "0": 1,
    "1": null,
    "2": null,
    "pick": 2,
    "correctpick": 0,
    "eliminated": 1
  },
  {
    "0": null,
    "1": null,
    "2": 1,
    "pick": 2,
    "correctpick": 2,
    "eliminated": 0
  }
]

As sloppy as the constructor for game looks, the reason is because I have refactored it into having as few function calls as possible, where now I'm literally only calling Math functions at the current time (I removed any helper functions that made the code easier to read, in opt for performance).

This app can be ran both in the browser and in node (cross platform), but I have clamped the arg a user can pass into the games function to 5 million. Any longer than that and the process (or window) freezes for longer than a few seconds, or even potentially crashes.

Is there anything else I can do to increase performance if a huge number is given by a user? Also, if you need more information, I will be happy to supply it!

Thanks!


Solution

  • Not sure about your implementation, but do you really need an Array?

    How about only using results (see snippet)?

    If it's blocking the browser that worries you, maybe delegating the work to a web worker is the solution for that: see this jsFiddle for a web worker version of this snippet.

    (() => {
      document.querySelector("#doit")
        .addEventListener("click", playMontyHall().handleRequest);
    
      function playMontyHall() {
        const result = document.querySelector("#result");
        const timing = document.querySelector("#timing");
        const nOfGames = document.querySelector("#nGames");
        const switchDoors = document.querySelector("#switchyn");
    
        // Create a game
        const game = (doSwitch) => {
          const doors = [0, 1, 2];
          const pick = Math.floor(Math.random() * 3);
          const correctPick = Math.floor(Math.random() * 3);
          const eliminated = doors.filter(v => v !== pick && v !== correctPick)[0];
    
          return {
            correctpick: correctPick,
            pick: doSwitch ? doors.filter(v => v !== pick && v !== eliminated)[0] : pick,
            eliminated: eliminated,
          };
        };
        
        const getWinner = game  => ~~(game.correctpick === game.pick);
        
        // Sum  wins using a generator function
        const winningGenerator = function* (doSwitch, n) {
          let wins = 0;
          
          while (n--) {
            wins += getWinner(game(doSwitch));
            yield wins;
          }
        };
    
        // calculate the number of succeeded games
        const calculateGames = (nGames, switchAlways) => {
          const funNGames = winningGenerator(switchAlways, nGames);
          let numberOfWins = 0;
          
          while (nGames--) {
            numberOfWins = funNGames.next().value;
          }
          
          return numberOfWins;
        }
        
        const cleanUp = playOut => {
            result.textContent =
            "Playing ... (it may last a few seconds)";
          timing.textContent = "";
          setTimeout(playOut, 0);
        };
        
        const report = results => {
            timing.textContent = `This took ${
             (performance.now() - results.startTime).toFixed(3)} milliseconds`;
          result.innerHTML =
             `<b>${!results.switchAlways ? "Never s" : "Always s"}witching doors</b>:
             ${results.winners} winners out of ${results.nGames} games
             (${((results.winners/+results.nGames)*100).toFixed(2)}%)`;
        };
        
        // (public) handle button click
        function clickHandle() {
         
          cleanUp(() => {
            const nGames = nOfGames.value || 5000000;
            const switchAlways = switchDoors.checked;
            report({
              switchAlways: switchAlways,
                startTime: performance.now(),
              winners: calculateGames(nGames, switchAlways),
              nGames: nGames
            });
          });
          
        }
    
        return {
          handleRequest: clickHandle
        };
      }
    
    })();
    body {
      margin: 2em;
      font: normal 12px/15px verdana, arial;
    }
    
    #timing {
      color: red;
    }
    <p><input type="number" id="nGames" value="5000000"> N of games</p>
    <p><input type="checkbox" id="switchyn"> Always switch doors</p>
    <p><button id="doit">Play</button>
    <p id="result"></p>
    <p id="timing"></p>