shuffle array w Go

82

Próbowałem przetłumaczyć następujący kod Pythona na Go

import random

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

ale okazało się, że moja wersja Go jest długa i niezręczna, ponieważ nie ma funkcji odtwarzania losowego i musiałem zaimplementować interfejsy i konwertować typy.

Jaka byłaby idiomatyczna wersja mojego kodu w Go?

demon
źródło
2
To pytanie ma implementację shuffle (): Traktowanie tablic w Go .
Sjoerd

Odpowiedzi:

96

Ponieważ twoja lista składa się tylko z liczb całkowitych od 1 do 25, możesz użyć Perm :

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

Zauważ, że użycie permutacji podanej przez rand.Permjest skutecznym sposobem na tasowanie dowolnej tablicy.

dest := make([]int, len(src))
perm := rand.Perm(len(src))
for i, v := range perm {
    dest[v] = src[i]
}
Denys Séguret
źródło
Nie jestem pewien, czy metoda Perm zmieniła się od czasu tej odpowiedzi, ale zwraca „pseudolosową permutację liczb całkowitych [0, n)”. W tym scenariuszu wynikiem byłaby permutacja od 0 do 24.
JayJay
1
@JayJay, dlatego liczby są zwiększane (innym rozwiązaniem byłaby zmiana z 0 na 25).
Denys Séguret
1
Przewijaj dalej w dół, jest to teraz obsługiwane po wyjęciu z pudełka w wersji 1.10: stackoverflow.com/a/46185753/474189
Duncan Jones
101

Odpowiedź dystroy jest całkowicie rozsądna, ale możliwe jest również tasowanie bez przydzielania dodatkowych plasterków.

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

Więcej informacji na temat algorytmu można znaleźć w tym artykule w Wikipedii . rand.Permw rzeczywistości używa tego algorytmu również wewnętrznie.

Evan Shaw
źródło
4
Rozumiem, że to jest wersja „na lewą stronę” w artykule, a ty unikasz i!=jczeku?
Matt Joiner,
Patrząc na stronę Wikipedii, wydaje się, że jest to „nowoczesny algorytm” (pierwszy wariant). Wersja „na zewnątrz” wydaje się przechowywać wynik w nowej tablicy, zamiast wykonywać tasowanie w miejscu.
jochen
49

Od 1.10 Go zawiera oficjalną funkcję tasowania Fisher-Yates .

Dokumentacja: pkg/math/rand/#Shuffle

math / rand: dodaj Shuffle

Shuffle używa algorytmu Fishera-Yatesa.

Ponieważ jest to nowe API, daje nam możliwość korzystania z dużo szybszej Int31nimplementacji, która w większości pozwala uniknąć podziału.

W rezultacie BenchmarkPerm30ViaShufflejest o około 30% szybszy niż BenchmarkPerm30pomimo wymagania oddzielnej pętli inicjalizacyjnej i używania wywołań funkcji do zamiany elementów.

Zobacz także oryginalny CL 51891

Po pierwsze, jak komentuje przez shelll :

Nie zapomnij wysiać losowo, bo zawsze otrzymasz tę samą kolejność.
Na przykładrand.Seed(time.Now().UnixNano()

Przykład:

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
źródło
@Deleplace Dziękuję. Zawarłem ten link w odpowiedzi.
VonC
3
Nie zapomnij wysiać losowo, bo zawsze otrzymasz tę samą kolejność. Na przykład rand.Seed(time.Now().UnixNano()).
shelll
@shelll Dziękuję. W odpowiedzi zawarłem Twój komentarz, aby uzyskać lepszą widoczność.
VonC
7

Odpowiedź Evana Shawa zawiera drobny błąd. Jeśli iterujemy przez wycinek od najniższego indeksu do najwyższego, aby uzyskać jednolite (pseudo) losowe tasowanie, zgodnie z tym samym artykułem , musimy wybrać losową liczbę całkowitą z przedziału, [i,n) a nie z[0,n+1) .

Ta implementacja zrobi to, czego potrzebujesz w przypadku większych danych wejściowych, ale w przypadku mniejszych wycinków wykona niejednorodne tasowanie.

Aby wykorzystać rand.Intn(), możemy zrobić:

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

zgodnie z tym samym algorytmem z artykułu z Wikipedii.

Społeczność
źródło
Jeśli odpowiedź zawiera błąd, edytuj złą odpowiedź, zamiast pisać kolejną odpowiedź.
Jacob Marble
2

Może możesz również użyć następującej funkcji:

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
źródło
1

Korzystając z math/randpakietu, nie zapomnij ustawić źródła

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

Napisałem więc Shufflefunkcję, która bierze to pod uwagę:

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

I aby z niego skorzystać:

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

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

Jeśli chcesz go użyć, możesz go znaleźć tutaj https://github.com/shomali11/util

Raed Shomali
źródło
1

Podejście Raeda jest bardzo nieelastyczne ze względu []interface{}na wkład. Oto wygodniejsza wersja dla 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)
    }
}

Przykładowe użycie:

    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]

Nie zapominaj też, że małe kopiowanie jest lepsze niż mała zależność

Joseph Buchma
źródło
1

Użyj Shuffle () z math/randbiblioteki.

Oto przykład:

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

Ponieważ pochodzi z math/randbiblioteki, należy go obsiać. Więcej informacji znajdziesz tutaj .

stwykd
źródło