I want to generate 4 random values from an array. How can I do it?

Asked

Viewed 170 times

2

I’m looking to generate 4 random values from an array. I’ve tried using Math.Random, but it’s in an infinite loop...

import React, { useState, useEffect } from "react";
import api from "../../services/api";

export default function Popular() {
  const [AllServices, setService] = useState([]);

  useEffect(() => {
    api.get("/").then((response) => {
      setService(response.data);
    });
  }, []);
  
    return (
      <div className="cards">
        {AllServices.slice(0, 4).map(service => (
          <div className="card" key={service.id}>
            <img src={service.imageUrl} alt={service.title} />
            <p className="title">{service.title}</p>
            <div className="info">
              <i className="material-icons">grade</i>
              <span className="rating">{service.rating}</span> &middot; {" "}
              <span className="category">{service.category}</span>
            </div>
          </div>
        ))}
      </div>
    );
}
  • @Danizavtz I didn’t understand, could explain me better?

  • I would like to know the range of the draw of the numbers, are random number from zero to ten?

  • @Danizavtz Ah, yes. From 0 to 8.

  • Where do you want to use these random values? instead of .slice(0, 4) to shorten the array? or want to have an array with random numbers inside? and how many? and 0 to 8?

  • @Sergio Instead of . Slice(), I want to pull 4 random values from the array. It would be 0 to the size of the array, in case 8.

  • Okay, and with Slice (as in the question) you have an infinite loop as referees or it works well?

  • @Sergio I managed to solve the infinite loop, now it’s just the question of generating the 4 random values

  • @Sergio It worked, thank you very much!

  • Note that this method produces flawed results. I made an answer that serves to get random results, if that is an important requirement.

Show 4 more comments

2 answers

3


You can do it like this (notes in code comments):

const getRandom = (arr, nr) => arr
  .slice() // criar uma cópia para não mudar a array inicial
  .sort(() => 0.5 - Math.random()) // misturar
  .slice(0, nr) // retirar N elementos da nova array misturada

const teste = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(getRandom(teste, 4));
console.log(getRandom(teste, 4));
console.log(getRandom(teste, 4));

In your code React could look like this:

import React, { useState, useEffect } from "react";
import api from "../../services/api";

const getRandom = (arr, nr) => arr
  .slice() // criar uma cópia para não mudar a array inicial
  .sort(() => 0.5 - Math.random()) // misturar
  .slice(0, nr) // retirar N elementos da nova array misturada

export default function Popular() {
  const [AllServices, setService] = useState([]);

  useEffect(() => {
    api.get("/").then((response) => {
      setService(response.data);
    });
  }, []);
  
  
  
  return (
    <div className="cards">
      {getRandom(AllServices, 4).map(service => (
        <div className="card" key={service.id}>
          <img src={service.imageUrl} alt={service.title} />
          <p className="title">{service.title}</p>
          <div className="info">
            <i className="material-icons">grade</i>
            <span className="rating">{service.rating}</span> &middot; {" "}
            <span className="category">{service.category}</span>
          </div>
        </div>
      ))}
    </div>
  );
}

2

The accepted answer works, but it has two small problems. The first, and largest, is that it produces k-permutations with non-uniform probabilities. By testing this function several times, it is clear that the result is addictive. Often, the numbers obtained are close to each other. If you really want a uniform k-permutation among all possible, then it is necessary to use another algorithm.

The other (minor) problem is performance, both in memory space and at runtime. The method Sort is typically run in quadratic time O(n²), and this is manifested when the original array is very large (with more than hundreds of thousands of elements). It also copies the original array unnecessarily, as we will see.

The solution to both problems is to use an adapted version of the Fisher-Yates. Fisher-Yates serves, at first, to shuffle a sequence of N numbers evenly, always counting that Math.random is also uniform in the range [0, 1).

That way, we have the implementation to follow:

const algoritmoFisher = (array, number) => {
  const copy = array.slice();
  const { length } = array;

  for (let i = length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    const temp = copy[i];
    copy[i] = copy[j];
    copy[j] = temp;
  }

  return copy.slice(0, number);
};

const algoritmoFisherAdaptado = (array, number) => {
  const shuffle = new Array(number);
  const { length } = array;

  shuffle[0] = array[0];

  for (let i = 1; i < number; i++) {
    const j = Math.floor(Math.random() * (i + 1));
    shuffle[i] = shuffle[j];
    shuffle[j] = array[i];
  }
  for (let i = number; i < length; i++) {
    const j = Math.floor(Math.random() * (i + 1));
    if (j <= number - 1) {
      shuffle[j] = array[i];
    }
  }

  return shuffle;
};

One can test the performance and observe the lack of uniformity of the naive algorithm in the demo I made in Codesandbox.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.