102

I tried to translate the following Python code to Go

import random

list = [i for i in range(1, 25)]
random.shuffle(list)
print(list)

but found my Go version lengthy and awkward because there is no shuffle function and I had to implement interfaces and convert types.

What would be an idiomatic Go version of my code?

0xcaff
  • 13,085
  • 5
  • 47
  • 55
deamon
  • 89,107
  • 111
  • 320
  • 448
  • 2
    This question has a shuffle() implementation: [Treatment of Arrays in Go](http://stackoverflow.com/questions/4221698/treatment-of-arrays-in-go). – Sjoerd Sep 04 '12 at 13:46

7 Answers7

117

dystroy's answer is perfectly reasonable, but it's also possible to shuffle without allocating any additional slices.

for i := range slice {
    j := rand.Intn(i + 1)
    slice[i], slice[j] = slice[j], slice[i]
}

See this Wikipedia article for more details on the algorithm. rand.Perm actually uses this algorithm internally as well.

Community
  • 1
  • 1
Evan Shaw
  • 23,839
  • 7
  • 70
  • 61
  • 4
    I take it this is the "inside-out" version in the article, and you elide the `i!=j` check? – Matt Joiner Mar 16 '14 at 12:10
  • Looking at the Wikipedia page, this seems to be the "modern algorithm" (first variant). The "inside-out" version seems to store the result in a new array, rather than doing the shuffle in place. – jochen Mar 19 '16 at 20:01
  • Caution: no, this isn't either "inside-out" or "modern". I advise against this suggestion. The "inside-out" algorithm works with the copy of the array/slice, i.e. operates two arrays/slices: `source` and `a` (shuffled). Here we have just one and it claims to operate in-place. It is also not "modern" either because "modern" should iterate from the end of the slice towards the beginning (excluding the very first element). Here it iterates from the first element till the end (including both). Either iteration direction or the way `j` is generated should change. – Mike Mar 23 '21 at 18:52
105

As your list is just the integers from 1 to 25, you can use Perm :

list := rand.Perm(25)
for i, _ := range list {
    list[i]++
}

Note that using a permutation given by rand.Perm is an effective way to shuffle any array.

dest := make([]int, len(src))
perm := rand.Perm(len(src))
for i, v := range perm {
    dest[v] = src[i]
}
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • I'm unsure if the Perm method has changed since this answer, but it returns "a pseudo-random permutation of the integers [0,n)". In this scenario, the outcome would be a permutation of 0 to 24. – JayJay Aug 17 '18 at 14:43
  • 1
    @JayJay that's why the numbers are incremented (another solution would have been to just change 0 to 25). – Denys Séguret Aug 17 '18 at 15:17
  • 1
    Keep scrolling down, this is now supported out the box in 1.10: https://stackoverflow.com/a/46185753/474189 – Duncan Jones May 23 '20 at 07:25
74

Since 1.10 Go includes an official Fisher-Yates shuffle function.

Documentation: pkg/math/rand/#Shuffle

math/rand: add Shuffle

Shuffle uses the Fisher-Yates algorithm.

Since this is new API, it affords us the opportunity to use a much faster Int31n implementation that mostly avoids division.

As a result, BenchmarkPerm30ViaShuffle is about 30% faster than BenchmarkPerm30, despite requiring a separate initialization loop and using function calls to swap elements.

See also the original CL 51891

First, as commented by shelll:

Do not forget to seed the random, or you will always get the same order.
For example rand.Seed(time.Now().UnixNano())

Example:

words := strings.Fields("ink runs from the corners of my mouth")
rand.Shuffle(len(words), func(i, j int) {
    words[i], words[j] = words[j], words[i]
})
fmt.Println(words)
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • @Deleplace Thank you. I have included this link in the answer. – VonC Feb 13 '18 at 10:05
  • 5
    Do not forget to seed the random, or you will always get the same order. For example `rand.Seed(time.Now().UnixNano())`. – shelll Dec 27 '19 at 07:58
  • @shelll Thank you. I have included your comment in the answer for more visibility. – VonC Dec 27 '19 at 08:19
10

Answer by Evan Shaw has a minor bug. If we iterate through the slice from lowest index to highest, to get a uniformly (pseudo) random shuffle, according to the same article, we must choose a random integer from interval [i,n) as opposed to [0,n+1).

That implementation will do what you need for larger inputs, but for smaller slices, it will perform a non-uniform shuffle.

To utilize rand.Intn(), we can do:

    for i := len(slice) - 1; i > 0; i-- {
        j := rand.Intn(i + 1)
        slice[i], slice[j] = slice[j], slice[i]
    }

following the same algorithm from Wikipedia article.

Community
  • 1
  • 1
5

Maybe you can also use the following function:

func main() {
   slice := []int{10, 12, 14, 16, 18, 20}
   Shuffle(slice)
   fmt.Println(slice)
}

func Shuffle(slice []int) {
   r := rand.New(rand.NewSource(time.Now().Unix()))
   for n := len(slice); n > 0; n-- {
      randIndex := r.Intn(n)
      slice[n-1], slice[randIndex] = slice[randIndex], slice[n-1]
   }
}
hkucuk
  • 345
  • 1
  • 4
  • 14
1

When using the math/rand package, do not forget to set a source

// Random numbers are generated by a Source. Top-level functions, such as
// Float64 and Int, use a default shared Source that produces a deterministic
// sequence of values each time a program is run. Use the Seed function to
// initialize the default Source if different behavior is required for each run.

So I wrote a Shuffle function that takes this into consideration:

import (
    "math/rand"
)

func Shuffle(array []interface{}, source rand.Source) {
    random := rand.New(source)
    for i := len(array) - 1; i > 0; i-- {
        j := random.Intn(i + 1)
        array[i], array[j] = array[j], array[i]
    }
}

And to use it:

source := rand.NewSource(time.Now().UnixNano())
array := []interface{}{"a", "b", "c"}

Shuffle(array, source) // [c b a]

If you would like to use it, you can find it here https://github.com/shomali11/util

Raed Shomali
  • 1,385
  • 15
  • 7
1

Raed's approach is very inflexible because of []interface{} as input. Here is more convenient version for go>=1.8:

func Shuffle(slice interface{}) {
    rv := reflect.ValueOf(slice)
    swap := reflect.Swapper(slice)
    length := rv.Len()
    for i := length - 1; i > 0; i-- {
            j := rand.Intn(i + 1)
            swap(i, j)
    }
}

Example usage:

    rand.Seed(time.Now().UnixNano()) // do it once during app initialization
    s := []int{1, 2, 3, 4, 5}
    Shuffle(s)
    fmt.Println(s) // Example output: [4 3 2 1 5]

And also, don't forget that a little copying is better than a little dependency

Joseph Buchma
  • 359
  • 4
  • 6