112

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?

1

8 Answers 8

129

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.

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

3 Comments

I take it this is the "inside-out" version in the article, and you elide the i!=j check?
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.
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.
108

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]
}

3 Comments

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 that's why the numbers are incremented (another solution would have been to just change 0 to 25).
Keep scrolling down, this is now supported out the box in 1.10: stackoverflow.com/a/46185753/474189
84

Go 1.20 (Q4 2022): as I mentioned in "How to properly seed random number generator", you no longer need rand.Seed() i the answer below.


2017: 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)

Comments

12

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.

1 Comment

If an answer has a bug then edit the wrong answer, instead of writing yet another answer.
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]
   }
}

Comments

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

Comments

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

Comments

-1

Simple but yet effective solution to the problem using closure swap function

package main

import (
    "math/rand"
)

func main() {
    var a = []int{1, 2, 3, 4, 5}
    rand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
}

2 Comments

Heads up: found this answer in the low quality answer queue. You might want to add an explanation of what it contributes on top of the other answers if you don't want to see it deleted.
@joanis added some description

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.