3

I have written JavaScript function, to shuffle an array of divs onClick.

  // Function to shuffle 3 divs
  shuffle = () => {
    const shuffled = this.state.divs.sort(() => Math.random() - .50);
    this.setState([...shuffled]);
  };

  // Button as an FYI
  <button onClick={this.shuffle} className="has-text-black">shuffle hats</button>

This works absolutely fine, and randomises every time I click the button.

However, I want the divs to sort/shuffle 5 times automatically, onClick.
(IE = I don't want to have to click 5 times, to shuffle 5 times).

What's the best approach to do this?
(I've searched but haven't found anything to repeat shuffling on elements).

I thought about using async await/settimeout, to repeat this.state.divs.sort(() => Math.random() - .50) 5 times?

UPDATE:

To add context, here is a codesandbox... https://codesandbox.io/s/distracted-shape-ifsiv

When I click the shuffle button, you can see the hats only swap positions once. Not 5 times.

Claudio Cortese
  • 1,372
  • 2
  • 10
  • 21
Reena Verma
  • 1,617
  • 2
  • 20
  • 47
  • 1
    I think shuffling one time and five times has the same effect. – Addis Dec 29 '19 at 17:37
  • 2
    [Fisher-Yates](https://en.m.wikipedia.org/wiki/Fisher–Yates_shuffle) is the preferred way to shuffle. – James Dec 29 '19 at 17:40
  • 1
    Why would 5 shuffles be any different than one shuffle? Assuming the shuffle algorithms is good you should get equally random result either way. Which does bring up that the current shuffle algorithm *isn't* random enough. As @James says, [the Fisher-Yates algorithm is way better](https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array). A "random" sorting is actually *less* random, since you're not guaranteed to get a thorough each element - sorting is (usually) optimised for less time complexity. – VLAZ Dec 29 '19 at 17:46

3 Answers3

2

Here is a posible way to do it, with javascript.

hats object get shuffled 5 times, with 200ms second transition.

Of course it's very simple, the object is meant to be extended!

let hats = [
  {content: `<h6>1</h6>`},
  {content: `<h3>2</h3>`},
  {content: `<h1>3</h1>`},
  {content: `<h2>4</h2>`},
  {content: `<h4>5</h4>`}
];
let timer;

function loop5(){
  let i = 0
  clearInterval(timer)
  timer = setInterval(function(){
    shuffle()
    if (i >= 5){
      clearInterval(timer)
    } 
    i++    
  }, 200)
}

function shuffle(){
   hats.sort(() => Math.random() - 0.5)
   out.innerHTML = hats.map(e => e.content).join("")
}
div {display: flex; font-size: xx-large }
<button onclick="loop5()">shuffle hats</button>
<div id="out"></div>
NVRM
  • 11,480
  • 1
  • 88
  • 87
  • 1
    What an amazing solution!! So simple and exactly what I was looking for. This is brilliant. Thank you again!!!! Really appreciate!!!! – Reena Verma Dec 29 '19 at 20:22
1

I don't see why shuffling 5 times is better or any different than shuffling 5 times. Anyway you can do it in a naive way like this:

// Function to shuffle 3 divs
  shuffle = () => {
    const shuffled = this.state.divs.sort(() => Math.random() - .50);
    this.setState([...shuffled]);
  };

  shuffleTimesFive = () => {
     for(var i = 0; i < 5; i++) 
         this.shuffle();
  }

  // Button as an FYI
  <button onClick={this.shuffleTimesFive} className="has-text-black">shuffle hats</button>

Or maybe a smarter way is to have a shuffleNTimes function that takes a parameter, like so:

  // Function to shuffle 3 divs
  shuffle = () => {
    const shuffled = this.state.divs.sort(() => Math.random() - .50);
    this.setState([...shuffled]);
  };

  shuffleNTimes = (n) => {
     for(var i = 0; i < n; i++) 
         this.shuffle();
  }

  // Button as an FYI
  <button onClick={this.shuffleNTimes.bind(this, 5)} className="has-text-black">shuffle hats</button>
Ismael Padilla
  • 5,246
  • 4
  • 23
  • 35
  • Thank you for this solution - it totally makes sense, but only a codesandbox.. Basically, when I click the shuffle button, I want the hats to shuffle automatically... 5 times... https://codesandbox.io/s/distracted-shape-ifsiv – Reena Verma Dec 29 '19 at 18:13
1

I think shuffling one time and five times has the same effect, and Fisher-Yates is more efficient but keeping your way:

  shuffle = () => {
    let shuffled = [];
    for(let i=0; i<5; i++) 
      shuffled = this.state.divs.sort(() => Math.random() - .50);
    this.setState([...shuffled]);
  };

If you decide to use "Fisher-Yates" algorithm, you can implement it like:

const shuffle = () => {
    let array = this.state.divs;
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    this.setState([...array]);
}

As I understood from your comment, you want to do animation, in this case you don't need a loop rather you can use setInterval() and reset it ones executed five times. I have written a demo on both shuffling ways, as you can see the method that uses sort() sometimes returns the same result while the "Fisher–Yates" always reshuffled.

<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>

<script>
//This one uses Fisher–Yates shuffle algorithm:
  const divs = [...document.querySelectorAll('div')];

  const shuffle = () => {
    let count = 0;
    const intervalId = setInterval( function() {
      for (let i = divs.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [divs[i], divs[j]] = [divs[j], divs[i]];
      }
      divs.forEach( div => document.body.appendChild(div) );
      count++;
      if(count === 5) 
        clearInterval(intervalId);
    } ,1000)
  }

</script>

<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>

<script>

  const divs = [...document.querySelectorAll('div')];

  shuffle = () => {
    let shuffled = [];
    let count = 0;
    const intervalId = setInterval( function() {
      shuffled = divs.sort(() => Math.random() - .50);
      shuffled.forEach( div => document.body.appendChild(div) );
      count++;
      if(count === 5) 
        clearInterval(intervalId);
    }, 1000 )
  };

</script>

For your case, it would be like:

  let divs = this.state.divs;

  const shuffle = () => {
    let count = 0;
    const intervalId = setInterval( function() {
      for (let i = divs.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [divs[i], divs[j]] = [divs[j], divs[i]];
      }
      this.setState([...divs]);
      count++;
      if(count === 5) 
        clearInterval(intervalId);
    } ,1000)
  }
Addis
  • 2,480
  • 2
  • 13
  • 21
  • you're right - it does have the same affect, so seems like it's only shuffling once. But thank you for sharing this. Why is it better to use Fisher Yates? I will give this a go. – Reena Verma Dec 29 '19 at 17:46
  • @ReenaVerma it's better since it's lightweight, fast, and *random*. [Sorting randomly doesn't](https://stackoverflow.com/questions/59521734/shuffle-array-automatically-5-times#comment105214544_59521734) – VLAZ Dec 29 '19 at 17:48
  • @ReenaVerma, also check out https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array/18650169#18650169 – Addis Dec 29 '19 at 17:52
  • Do I need to add an animation? Is this why the shuffling < 5 does not work? – Reena Verma Dec 29 '19 at 18:35
  • What do you mean by " the shuffling < 5 does not work "? Do you mean why shuffling 5 times has the same effect as shuffling 1 time? – Addis Dec 29 '19 at 18:39
  • If your intention is to make an animation of the shuffling, then the "5 time" shuffling would be logical. But you would have to do it at some interval for the changes to be noticed. – Addis Dec 29 '19 at 18:42