8

It is not hard to shuffle an array in O(n), with in place swapping,

How to do it for list in OCaml, with O(n)?


Requirement:

  1. No array or in place usage

  2. Consider this as an interview question

Jackson Tale
  • 25,428
  • 34
  • 149
  • 271

2 Answers2

14

Lists are immutable, and there's often a log n price to pay for working with immutable data. If you're willing to pay this cost, there's an obvious n log n approach: tag each list element with a random value, sort based on random value, remove random values. This is the way I shuffle lists in my production code.

Here is the shuffle code from the iOS apps that I sell:

let shuffle d =
    let nd = List.map (fun c -> (Random.bits (), c)) d in
    let sond = List.sort compare nd in
    List.map snd sond
Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
  • Yes, I thought so too. The interesting thing is that this question popped up to me as I am going to write quicksort. for `quicksort`, I have shuffle the list first, then do the partition, etc. In this case, how do I sort the random values in your code? – Jackson Tale Feb 26 '13 at 17:44
  • 1
    Note that if you shuffle using sorting, not all permutations are equally likely. See this: http://okmij.org/ftp/Haskell/perfect-shuffle.txt – sepp2k Feb 26 '13 at 17:47
  • 3
    It seems the deviations arise when tags are equal. With 63 bits of tag this is a small deviation. (I read that message from Oleg when it came out. He is awesome.) – Jeffrey Scofield Feb 26 '13 at 17:49
  • @sepp2k I think the negative point of the attach_value-then-sort algorithm that message was talking about can be easily `fixed by` that `while sorting the attached values, if two values are equal, then generate another 0|1 to decide who comes in front`, i.e., we don't want stable sort. Am I correct? how anyway, that message is correct, if stable sort is used – Jackson Tale Feb 26 '13 at 18:03
  • Yeah, I always forgot List.map, List.filter, List.fold things. Have to practice more. – Jackson Tale Feb 26 '13 at 18:17
  • do we need to call `Random.self_init`? – Jackson Tale Feb 27 '13 at 17:06
  • Yes, if you want a different shuffle each time you run the program. – Jeffrey Scofield Feb 27 '13 at 17:10
0

You could mimick the riffle shuffle for cards.

A riffle shuffle of a deck of cards means to:

  • cut the deck in two parts
  • interleave the two parts

It is actually easier to do the reverse permutation:

  • have two auxiliary lists A and B, iter through your original list L and push each element randomly (with probability 1/2) in front of A or B.
  • L := List.rev A @ List.rev B (this can be tail recursive with a custom List.rev).
  • repeat k times.

According to "Mathematical developments from the analysis of riffle shuffling, by Persi Diaconis, 2002", choose k = 3/2 log_2(n) + c. Indeed, the total variation distance between uniformity and the result falls exponentially fast to 0: it is approximately halved each time you increment c. You could choose c=10.

Space O(1) (if you destroy L), time O(n log n). But there are O(n log n) calls to the random generator, while Jeffrey Scofield's solution only needs O(n) random bits, but Θ(n) space.

jrouquie
  • 4,315
  • 4
  • 27
  • 43