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.

3
  • 1
    I think shuffling one time and five times has the same effect. Commented Dec 29, 2019 at 17:37
  • 2
    Fisher-Yates is the preferred way to shuffle. Commented Dec 29, 2019 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. 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. Commented Dec 29, 2019 at 17:46

3 Answers 3

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>

Sign up to request clarification or add additional context in comments.

1 Comment

What an amazing solution!! So simple and exactly what I was looking for. This is brilliant. Thank you again!!!! Really appreciate!!!!
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>

1 Comment

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... codesandbox.io/s/distracted-shape-ifsiv
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)
  }

6 Comments

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.
@ReenaVerma it's better since it's lightweight, fast, and random. Sorting randomly doesn't
@ReenaVerma, also check out stackoverflow.com/questions/2450954/…
Do I need to add an animation? Is this why the shuffling < 5 does not work?
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?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.