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 2012

回答:


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の順列になります。
JayJay18年

1
@JayJayは、数値が増分される理由です(別の解決策は、0から25に変更することでした)。
デニス・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]
}

参照してください。このWikipediaの記事アルゴリズムの詳細については、を。rand.Perm実際には、このアルゴリズムも内部で使用しています。


4
これは記事の「裏返し」バージョンだと思いますが、i!=jチェックを省略しますか?
マットジョイナー2014年

ウィキペディアのページを見ると、これは「最新のアルゴリズム」(最初のバリアント)のようです。「裏返し」バージョンは、シャッフルを実行するのではなく、結果を新しい配列に格納しているようです。
jochen 2016年

49

1.10以降、Goには公式のFisher-Yatesシャッフル機能が含まれています。

ドキュメンテーション: pkg/math/rand/#Shuffle

数学/ランド:シャッフルを追加

シャッフルはフィッシャー-イェーツアルゴリズムを使用します。

これは新しいAPIであるため、Int31n分割をほとんど回避するはるかに高速な実装を使用する機会が得られます。

その結果、個別の初期化ループが必要であり、関数呼び出しを使用して要素を交換しているにもかかわらず、BenchmarkPerm30ViaShuffleBenchmarkPerm30、よりも約30%高速です。

オリジナルのCL51891も参照してください

まず、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 2018

3
ランダムにシードすることを忘れないでください。そうしないと、常に同じ順序になります。たとえばrand.Seed(time.Now().UnixNano())
shelll

@shelllありがとうございます。わかりやすくするために、回答にコメントを含めました。
VonC

7

EvanShawによる回答には小さなバグがあります。最低インデックスから最高にスライスを通じて我々反復を均一に(擬似)ランダムシャッフルを取得する場合は、によると、同じ記事で、我々は間隔からランダムな整数を選択する必要があります[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ライブラリーには、播種する必要があります。詳細については、こちらをご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.