skip to Main Content

I have an array of objects of countries

const countries = [
  {
    capital: "Kabul",
    countryISOCode: "af",
    continent: "Asia",
    countryFullName: "Afghanistan",
  },
  {
    capital: "Mariehamn",
    countryISOCode: "ax",
    continent: "Europe",
    countryFullName: "Aland Islands",
  },
  {
    capital: "Tirana",
    countryISOCode: "al",
    continent: "Europe",
    countryFullName: "Albania",
  },
  {
    capital: "Algiers",
    countryISOCode: "dz",
    continent: "Africa",
    countryFullName: "Algeria",
  },
  {
    capital: "Pago Pago",
    countryISOCode: "as",
    continent: "Oceania",
    countryFullName: "American Samoa",
  },
  {
    capital: "Andorra la Vella",
    countryISOCode: "ad",
    continent: "Europe",
    countryFullName: "Andorra",
  }
]

I want to randomly select an object which I currently do with
const randomCountry = Math.floor(Math.random() * countries.length);

Problem is that there are often duplicates, i.e. the same country gets chosen twice in a row. I want to add that a country is not able to be selected again for x amount of random selections to make sure it does not appear so often. What would be the best approach to make this work?

4

Answers


  1. If you are looking for a list of random countries then you can consider using a Set

    If not you can add the country to a set you store in a variable and then when selecting a new country check if it exists in the set and if it does select a new one.

    From the MDN page:

    const mySet1 = new Set();
    
    mySet1.add(1); // Set(1) { 1 }
    mySet1.add(5); // Set(2) { 1, 5 }
    mySet1.add(5); // Set(2) { 1, 5 }
    mySet1.add("some text"); // Set(3) { 1, 5, 'some text' }
    
    mySet1.has(1); // true
    mySet1.has(3); // false, since 3 has not been added to the set
    mySet1.has(5); // true
    
    Login or Signup to reply.
  2. An obvious solution at first thought comes to my mind is adding another property to data structure of countries which will store the data of count of selection.

    We can call this property timesSelected. Whenever an object gets randomly selected we will increment this property by 1. If you don’t want a country to be selected more than, lets say twice, we can confitionally check if timesSelected property’s value is less than 2.

    const countries = [
      {
        capital: "Kabul",
        countryISOCode: "af",
        continent: "Asia",
        countryFullName: "Afghanistan",
        timesSelected: 0
      },
      {
        capital: "Mariehamn",
        countryISOCode: "ax",
        continent: "Europe",
        countryFullName: "Aland Islands",
        timesSelected: 0
      },
      {
        capital: "Tirana",
        countryISOCode: "al",
        continent: "Europe",
        countryFullName: "Albania",
        timesSelected: 0
      },
      {
        capital: "Algiers",
        countryISOCode: "dz",
        continent: "Africa",
        countryFullName: "Algeria",
        timesSelected: 0
      },
      {
        capital: "Pago Pago",
        countryISOCode: "as",
        continent: "Oceania",
        countryFullName: "American Samoa",
        timesSelected: 0
      },
      {
        capital: "Andorra la Vella",
        countryISOCode: "ad",
        continent: "Europe",
        countryFullName: "Andorra",
        timesSelected: 0
      }
    ]
    
    const randomlySelectACountry = () => {
      const randomCountryIndex = Math.floor(Math.random() * countries.length);
      const countrySelected = countries[randomCountryIndex];
    
      // Check if the timesSelected proeprty's value is greater than or equal to 2
      if(countrySelected.timesSelected >= 2) {
        return randomlySelectACountry();
      }
      
      // Increment timesSelected proeprty of selected country by 1
      countrySelected.timesSelected++;
    
      return countrySelected;
    };
    

    Important note: In this code randomlySelectACountry function calls itself if the randomly selected country’s timesSelected property’s value is greater than or equal to 2. This selection code is not optiomal since it can still randomly select countries with timesSelected property’s value greater than or equal to 2. To optimize and prevent this behaviour, we can either filter the countries array to match only country which have timesSelected property’s value is less than 2 or we can filter out whenever a selected country’s timesSelected proeprty’s value becomes greater than 2. First approach gives you possibility to have countries array untouched (except of timesSelected property of course) and second approach reduces the countries array each time a country has been selected at least 2 times.

    Login or Signup to reply.
  3. In the snippet below, the arrayRandomizer is an object with a get() method which will always produce a random number from the input array. The chance of a duplicate decreases as the cacheSize is increased. Each item is selected exactly one time, before re-indexing occurs. The cache remembers the previously selected items.

    In the example below, the cacheSize is set to 2. This means that a duplicate will not appear until a third selection is made.

    Note: I kept log statements, if you would like to debug the state of the randomizer each time you request another value.

    const main = () => {
      //console.log('START');
      let prev;
      const randomizer = arrayRandomizer(countries, 2);
      for (let i = 0; i < 100; i++) {
        const [index, country] = randomizer.get();
        if (country === prev) {
          //console.error('PREV!');
        }
        console.log(index, format(country));
        prev = country;
      }
      //console.log('DONE');
    };
    
    const randSort = () => 0.5 - Math.random();
    
    const arrayRandomizer = function(arr, cacheSize = 1) {
      const randomize = () =>
        Array.from(arr, (_, i) => i).sort(randSort);
        
      let randomIndices = randomize();
      let cache = new Set();
    
      return ({
        get() {
          const [currIndex] = randomIndices.splice(0, 1);
          const next = arr[currIndex];
          const delta = arr.length - (arr.length - randomIndices.length);
          if (delta < cacheSize) {
            //console.error('ADDING TO CACHE:', currIndex, 'DELTA:', delta);
            cache.add(currIndex);
          }
          if (randomIndices.length === 0) {
            const newIndices = randomize();
            randomIndices = [
              ...newIndices.filter(n => !cache.has(n)), // Non-cached, up front
              ...newIndices.filter(n => cache.has(n)),  // Cached, at the end
            ];
            //console.error('PREV CACHE:', ...[...cache], 'NEW INDICES:', ...randomIndices);
            cache.clear();
          }
          return [currIndex, next];
        }
      })
    };
    
    const format = ({ capital, countryFullName }) =>
      `${capital}, ${countryFullName}`;
    
    const countries = [{
      capital: "Kabul",
      countryISOCode: "af",
      continent: "Asia",
      countryFullName: "Afghanistan",
    }, {
      capital: "Mariehamn",
      countryISOCode: "ax",
      continent: "Europe",
      countryFullName: "Aland Islands",
    }, {
      capital: "Tirana",
      countryISOCode: "al",
      continent: "Europe",
      countryFullName: "Albania",
    }, {
      capital: "Algiers",
      countryISOCode: "dz",
      continent: "Africa",
      countryFullName: "Algeria",
    }, {
      capital: "Pago Pago",
      countryISOCode: "as",
      continent: "Oceania",
      countryFullName: "American Samoa",
    }, {
      capital: "Andorra la Vella",
      countryISOCode: "ad",
      continent: "Europe",
      countryFullName: "Andorra",
    }];
    
    main();
    .as-console-wrapper { top: 0; max-height: 100% !important; }
    Login or Signup to reply.
  4. Use a stack and pool scenario:

    let pool = countries.slice(), stack = [];
    const STACK_SIZE = 4;
    
    let count = 30;
    while(count--){
      const idx = Math.random() * pool.length | 0;
      console.log(pool[idx].countryFullName);
      stack.push(...pool.splice(idx, 1));
      if(stack.length > STACK_SIZE){
        pool.push(stack.shift());
      }
    }
    <script>
    const countries = [
      {
        capital: "Kabul",
        countryISOCode: "af",
        continent: "Asia",
        countryFullName: "Afghanistan",
        timesSelected: 0
      },
      {
        capital: "Mariehamn",
        countryISOCode: "ax",
        continent: "Europe",
        countryFullName: "Aland Islands",
        timesSelected: 0
      },
      {
        capital: "Tirana",
        countryISOCode: "al",
        continent: "Europe",
        countryFullName: "Albania",
        timesSelected: 0
      },
      {
        capital: "Algiers",
        countryISOCode: "dz",
        continent: "Africa",
        countryFullName: "Algeria",
        timesSelected: 0
      },
      {
        capital: "Pago Pago",
        countryISOCode: "as",
        continent: "Oceania",
        countryFullName: "American Samoa",
        timesSelected: 0
      },
      {
        capital: "Andorra la Vella",
        countryISOCode: "ad",
        continent: "Europe",
        countryFullName: "Andorra",
        timesSelected: 0
      }
    ]
    </script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search