перетасувати масив у Go


82

Я спробував перевести наступний код Python на Go

import random

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

але моя версія Go виявилася тривалою та незграбною, оскільки немає функції перетасування, і мені довелося реалізовувати інтерфейси та конвертувати типи.

Що буде ідіоматичною версією Go мого коду?


2
Це питання має реалізацію перетасовки (): Обробка масивів у Go .
Sjoerd

Відповіді:


96

Оскільки у вашому списку є лише цілі числа від 1 до 25, ви можете використовувати Перм :

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

Відповідь Евана Шоу має незначну помилку. Якщо ми перебираємо зріз від найнижчого індексу до найвищого, щоб отримати рівномірно (псевдо) випадкове перетасовку, відповідно до тієї ж статті , ми повинні вибрати випадкове ціле число з інтервалу [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]
    }

дотримуючись того ж алгоритму зі статті Вікіпедії.


Якщо у відповіді є помилка, відредагуйте неправильну відповідь, замість того, щоб писати ще одну відповідь.
Jacob Marble

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

Підхід Раеда дуже негнучкий, оскільки []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.