перемешать массив в Go


82

Я попытался перевести следующий код Python на Go

import random

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

но моя версия Go показалась мне длинной и неудобной, потому что здесь нет функции перемешивания, и мне пришлось реализовать интерфейсы и преобразовать типы.

Какой была бы идиоматическая версия моего кода для Go?


2
В этом вопросе реализована реализация shuffle (): обработка массивов в Go .
Sjoerd

Ответы:


96

Поскольку ваш список состоит только из целых чисел от 1 до 25, вы можете использовать Perm :

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

Обратите внимание, что использование перестановки, заданной параметром, rand.Permявляется эффективным способом перемешивания любого массива.

dest := make([]int, len(src))
perm := rand.Perm(len(src))
for i, v := range perm {
    dest[v] = src[i]
}

Я не уверен, изменился ли метод Perm после этого ответа, но он возвращает «псевдослучайную перестановку целых чисел [0, n)». В этом сценарии результатом будет перестановка от 0 до 24.
JayJay

1
@JayJay, поэтому числа увеличиваются (другим решением было бы просто изменить 0 на 25).
Denys Séguret

1
Продолжайте прокручивать вниз, теперь это поддерживается в версии 1.10: stackoverflow.com/a/46185753/474189
Дункан Джонс,

101

Ответ dystroy вполне разумен, но также можно перетасовать без выделения каких-либо дополнительных фрагментов.

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

См. Эту статью в Википедии для получения более подробной информации об алгоритме. rand.Permфактически использует этот алгоритм и внутри.


4
Я так понимаю, что это «вывернутая наизнанку» версия статьи, а вы не прошли i!=jпроверку?
Matt Joiner

Глядя на страницу Википедии, кажется, что это «современный алгоритм» (первый вариант). Версия «наизнанку», кажется, сохраняет результат в новом массиве, а не выполняет перемешивание на месте.
jochen

49

Начиная с версии 1.10 Go включает в себя официальную функцию перемешивания Fisher-Yates .

Документация: pkg/math/rand/#Shuffle

math / rand: добавить Shuffle

Shuffle использует алгоритм Фишера-Йейтса.

Поскольку это новый API, он дает нам возможность использовать гораздо более быструю Int31nреализацию, которая в основном позволяет избежать разделения.

В результате BenchmarkPerm30ViaShuffleэто примерно на 30% быстрее, чем BenchmarkPerm30, несмотря на необходимость отдельного цикла инициализации и использование вызовов функций для обмена элементами.

См. Также оригинальный CL 51891

Прежде всего , как заметил по shelll :

Не забудьте засеять случайным образом, иначе вы всегда будете получать одинаковый порядок.
Напримерrand.Seed(time.Now().UnixNano()

Пример:

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)

@Deleplace Спасибо. Я включил эту ссылку в ответ.
VonC

3
Не забудьте засеять случайным образом, иначе вы всегда будете получать одинаковый порядок. Например rand.Seed(time.Now().UnixNano()).
shelll

@shelll Спасибо. Я включил ваш комментарий в ответ для большей наглядности.
VonC

7

Ответ Эвана Шоу содержит небольшую ошибку. Если мы перебираем срез от наименьшего индекса к наибольшему, чтобы получить равномерно (псевдо) случайное перемешивание, согласно той же статье , мы должны выбрать случайное целое число от interval, [i,n) а не от[0,n+1) .

Эта реализация будет делать то, что вам нужно для больших входных данных, но для меньших фрагментов она будет выполнять неравномерное перемешивание.

Чтобы использовать rand.Intn(), мы можем:

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

по тому же алгоритму из статьи в Википедии.


Если в ответе есть ошибка, отредактируйте неправильный ответ вместо того, чтобы писать еще один ответ.
Джейкоб Марбл

2

Возможно, вы также можете использовать следующую функцию:

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

1

При использовании math/randпакета не забудьте указать источник

// 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.

Поэтому я написал Shuffleфункцию, которая учитывает это:

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

И использовать это:

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

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

Если вы хотите его использовать, вы можете найти его здесь https://github.com/shomali11/util


1

Подход Raed очень негибкий из-за []interface{}входных данных. Вот более удобная версия для 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)
    }
}

Пример использования:

    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]

И также не забывайте, что небольшое копирование лучше, чем небольшая зависимость.


1

Используйте Shuffle () из math/randбиблиотеки.

Вот пример:

package main

import (
    "fmt"
    "math/rand"
    "strings"
)

func main() {
    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)
}

Поскольку он поступает из math/randбиблиотеки, его необходимо засеять. Подробнее см. Здесь .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.