2
\$\begingroup\$

I know randomness in programming can be a deep topic, and searching the topic seems to result in Fisher-Yates being the best method, but I'm curious what people would say about this method.

Basically, since I have the cards as objects, I just add a property of a random number then sort the entire array based on those random numbers.

const suits = [
  {
    digit: 'H',
    word: 'hearts'
  },
  {
    digit: 'C',
    word: 'clubs'
  },
  {
    digit: 'D',
    word: 'diamonds'
  },
  {
    digit: 'S',
    word: 'spades'
  }
]
const cardsWithoutSuits = [
  {
    numeric: 1,
    word: 'ace',
    digit: 'A'
  },
  {
    numeric: 2,
    word: 'two',
  },
  {
    numeric: 3,
    word: 'three',
  },
  {
    numeric: 4,
    word: 'four',
  },
  {
    numeric: 5,
    word: 'five',
  },
  {
    numeric: 6,
    word: 'six',
  },
  {
    numeric: 7,
    word: 'seven',
  },
  {
    numeric: 8,
    word: 'eight',
  },
  {
    numeric: 9,
    word: 'nine',
  },
  {
    numeric: 10,
    word: 'ten',
  },
  {
    numeric: 11,
    word: 'jack',
    digit: 'J'
  },
  {
    numeric: 12,
    word: 'queen',
    digit: 'Q'
  },
  {
    numeric: 13,
    word: 'king',
    digit: 'K'
  }
]

function createDeck(decks = 1){
  let deck = [];
  for (let i = 0; i < decks; i++) {
    suits.forEach( x => {
      cardsWithoutSuits.forEach( y => {
        deck.push({
          numeric: y.numeric,
          word: y.word,
          suit: x.word,
          phrase: `${y.word} of ${x.word}`,
          abbr: `${y.hasOwnProperty('digit') ? y.digit : y.numeric}${x.digit}`
        })
      })
    })
  }
  return deck;
}

function shuffle(array){
  array.forEach( x =>{
    x.ran = Math.random();
  })
  array.sort( (a, b) =>{
    return a.ran - b.ran;
  })

  return array;
}

let deck = shuffle(createDeck(2));
console.log(deck);

\$\endgroup\$
  • \$\begingroup\$ Here is a version in codepen: codepen.io/LouBagel/pen/WPrzjy \$\endgroup\$ – Lou Bagel Jan 25 at 21:38
  • 1
    \$\begingroup\$ The time complexity of this algorithm is O(n*lg(n)) while Fisher-Yates and friends are linear in n. And a big problem with pseudo-random number generators as shufflers is that they have far fewer possible permutations than a deck of cards. 52 factorial is about 8e67; compare to Chrome's Math.random() with 128 bits of internal state - about 3e38 possible states. For a given real-world deck ordering, the odds are only about 1 in a nonillion that your algorithm can produce it in a better-than-average JavaScript engine. \$\endgroup\$ – Oh My Goodness Jan 26 at 8:19
  • \$\begingroup\$ @OhMyGoodness The size of the random does not limit the permutations, rather it limits the length of a sequence without a predictable repeating pattern.; Games of chance play with as little as 1 bit (coin) yet will still require a sufficiently large PRNG to ensure the game can not be exploited. With dedication, an unaided human will be hard pushed to identify pattern from a 16bit PRNG \$\endgroup\$ – Blindman67 Jan 29 at 7:15
  • \$\begingroup\$ @Blindman67 2^128 seeds cannot yield more than 2^128 distinct initial sequences of 52 numbers from a deterministic algorithm. 2^128 <<< 52!, the end. Unless you're saying we don't need to use the first 52 numbers? Which is true, but you have to choose which 52 to use, somehow, and it has to be unpredictable, and if you can choose 52 things unpredictably, just make those things be "cards" instead of "PRNG cycles" and then you don't need the PRNG at all. \$\endgroup\$ – Oh My Goodness Jan 29 at 7:37
0
\$\begingroup\$

There has already been some discussion by @Oh My Goodness and @Blindman67 in the comments about the time complexity of this code vs Fisher-Yates. I don't really have much to add to that discussion but will offer some suggestions about the code on a deeper level below.

Like I mentioned in my answer to your post Simple math game in JavaScript some variable names could more accurately hint at the semantic meaning of the value - for example, instead of x.ran = Math.random();, a name like sortVal would hint that the value is to be used for sorting.

array.forEach( x =>{
  x.sortVal = Math.random();
})

The arrow function in the shuffle() method is quite short and thus can be simplified to a single line:

array.sort( (a, b) => a.sortVal - b.sortVal)

The forEach block above it could also be simplified:

array.forEach( x => x.sortVal = Math.random())

In this case, x.sortVal would be returned in each iteration but that doesn't make a difference.


The Array sort method Array.prototype.sort() returns the array so the shuffle() function could be simplified to have the return statement on the same line as the call to that method:

  return array.sort( (a, b) => a.sortVal - b.sortVal)

Additionally, any value that doesn't get re-assigned could be declared using const, which would help avoid any unintentional re-assignment - e.g. deck within shuffle(), and deck at the end of the code.

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.