Como gerar uma sequência aleatória de comprimento fixo no Go?


300

Quero apenas uma sequência aleatória de caracteres (maiúsculas ou minúsculas), sem números, no Go. Qual é a maneira mais rápida e simples de fazer isso?


2
@VinceEmigh: Aqui está um meta tópico discutindo questões básicas. meta.stackoverflow.com/q/274645/395461 Pessoalmente, acho que as perguntas básicas são válidas se bem escritas e estão no tópico. Veja as respostas abaixo, elas ilustram várias coisas que seriam úteis para alguém novo. Para loops, tipo fundição, make (), etc.
Shannon Matthews

2
@ Shannon " Esta pergunta não mostra nenhum esforço de pesquisa " (primeira resposta altamente votada no seu link) - É a isso que eu estava me referindo. Ele não mostra nenhum esforço de pesquisa. Nenhum esforço (uma tentativa, ou mesmo afirmando que ele estava online, o que obviamente ele não fez). Embora seja útil para alguém novo , este site não está focado em ensinar novas pessoas. Ele se concentra em responder a problemas / perguntas de programação específicos, não a tutoriais / guias. Embora possa ser usado para este último, esse não é o foco e, portanto, essa questão deve ser encerrada. Em vez disso, / /
Spooneded

9
@VinceEmigh Fiz essa pergunta há um ano. Eu tinha pesquisado on-line por seqüências aleatórias e lido documentos também. Mas não foi útil. Se eu não escrevi na pergunta, isso não significa que não pesquisei.
Anish Shah

Respostas:


809

A solução de Paul fornece uma solução simples e geral.

A pergunta pede o "caminho mais rápido e mais simples" . Vamos abordar a parte mais rápida também. Chegamos ao nosso código final mais rápido de maneira iterativa. O benchmarking de cada iteração pode ser encontrado no final da resposta.

Todas as soluções e o código de benchmarking podem ser encontrados no Go Playground . O código no Playground é um arquivo de teste, não um executável. Você precisa salvá-lo em um arquivo chamado XX_test.goe executá-lo com

go test -bench . -benchmem

Prefácio :

A solução mais rápida não é uma solução essencial se você só precisa de uma sequência aleatória. Para isso, a solução de Paulo é perfeita. Isto é, se o desempenho importa. Embora as duas primeiras etapas ( bytes e restante ) possam ser um compromisso aceitável: elas melhoram o desempenho em 50% (consulte os números exatos na seção II. Benchmark ) e não aumentam significativamente a complexidade.

Dito isto, mesmo que você não precise da solução mais rápida, a leitura desta resposta pode ser aventureira e educativa.

I. Melhorias

1. Gênesis (Runas)

Como lembrete, a solução geral original que estamos melhorando é a seguinte:

func init() {
    rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[rand.Intn(len(letterRunes))]
    }
    return string(b)
}

2. Bytes

Se os caracteres para escolher e montar a sequência aleatória contiverem apenas as letras maiúsculas e minúsculas do alfabeto inglês, poderemos trabalhar com bytes apenas porque as letras do alfabeto inglês são mapeadas para os bytes 1 a 1 na codificação UTF-8 (que é como o Go armazena as strings).

Então, em vez de:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

podemos usar:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Ou melhor ainda:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Agora, isso já é uma grande melhoria: podemos conseguir que seja um const(existem stringconstantes, mas não existem constantes de fatia ). Como um ganho extra, a expressão len(letters)também será a const! (A expressão len(s)é constante se sfor uma constante de sequência.)

E a que custo? Nada mesmo. strings podem ser indexados, o que indexa seus bytes, perfeito, exatamente o que queremos.

Nosso próximo destino é assim:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Intn(len(letterBytes))]
    }
    return string(b)
}

3. Restante

As soluções anteriores obtêm um número aleatório para designar uma letra aleatória chamando rand.Intn()quais delegados para Rand.Intn()quais delegados Rand.Int31n().

Isso é muito mais lento comparado ao rand.Int63()que produz um número aleatório com 63 bits aleatórios.

Assim, poderíamos simplesmente chamar rand.Int63()e usar o restante depois de dividir por len(letterBytes):

func RandStringBytesRmndr(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Isso funciona e é significativamente mais rápido, a desvantagem é que a probabilidade de todas as letras não será exatamente a mesma (supondo que rand.Int63()produz todos os números de 63 bits com igual probabilidade). Embora a distorção seja extremamente pequena, pois o número de letras 52é muito menor do que 1<<63 - 1, na prática, isso é perfeitamente adequado.

Para facilitar a compreensão: digamos que você queira um número aleatório no intervalo de 0..5. Usando 3 bits aleatórios, isso produziria os números 0..1com probabilidade dupla do que a partir do intervalo 2..5. Usando 5 bits aleatórios, os números no intervalo 0..1ocorreriam com 6/32probabilidade e os números no intervalo 2..5com 5/32probabilidade que agora estão mais próximos do desejado. Aumentar o número de bits torna isso menos significativo, ao atingir 63 bits, é insignificante.

4. Mascaramento

Com base na solução anterior, podemos manter a distribuição igual de letras usando apenas o menor número de bits do número aleatório necessário para representar o número de letras. Por exemplo, se temos 52 cartas, que exige 6 bits para representá-lo: 52 = 110100b. Portanto, usaremos apenas os 6 bits mais baixos do número retornado por rand.Int63(). E para manter uma distribuição igual de letras, apenas "aceitamos" o número se ele estiver dentro do intervalo 0..len(letterBytes)-1. Se os bits mais baixos forem maiores, descartamos e consultamos um novo número aleatório.

Observe que a chance dos bits mais baixos serem maiores ou iguais a len(letterBytes)é menor do que 0.5em geral ( 0.25em média), o que significa que, mesmo que esse fosse o caso, repetir esse caso "raro" diminui a chance de não encontrar um bom número. Após a nrepetição, a chance de não termos um bom índice é muito menor do que pow(0.5, n), e essa é apenas uma estimativa superior. No caso de 52 letras, a chance de os 6 bits mais baixos não serem bons é apenas (64-52)/64 = 0.19; o que significa, por exemplo, que as chances de não ter um bom número após 10 repetições são1e-8 .

Então, aqui está a solução:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func RandStringBytesMask(n int) string {
    b := make([]byte, n)
    for i := 0; i < n; {
        if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i++
        }
    }
    return string(b)
}

5. Mascaramento Melhorado

A solução anterior usa apenas os 6 bits mais baixos dos 63 bits aleatórios retornados por rand.Int63() . Isso é um desperdício, pois obter os bits aleatórios é a parte mais lenta do nosso algoritmo.

Se tivermos 52 letras, isso significa que 6 bits codificam um índice de letras. Portanto, 63 bits aleatórios podem designar 63/6 = 10diferentes índices de letras. Vamos usar todos esses 10:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func RandStringBytesMaskImpr(n int) string {
    b := make([]byte, n)
    // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = rand.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

6. Fonte

O Masking Improved é muito bom, não podemos melhorar muito. Poderíamos, mas não vale a pena a complexidade.

Agora vamos encontrar outra coisa para melhorar. A fonte de números aleatórios.

Há um crypto/randpacote que fornece uma Read(b []byte)função, então podemos usá-lo para obter quantos bytes com uma única chamada for necessário. Isso não ajudaria em termos de desempenho, poiscrypto/rand implementa um gerador de números pseudo-aleatórios criptograficamente seguros, por isso é muito mais lento.

Então, vamos nos ater ao math/randpacote. O rand.Randusa a rand.Sourcecomo fonte de bits aleatórios. rand.Sourceé uma interface que especifica um Int63() int64método: exatamente e a única coisa que precisamos e usamos em nossa solução mais recente.

Portanto, não precisamos realmente de um rand.Rand(explícito ou global, compartilhado do randpacote), a rand.Sourceé perfeitamente suficiente para nós:

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Observe também que esta última solução não exige que você inicialize (propague) o global Randdo math/randpacote, pois ele não é usado (e nossarand.Source é adequadamente inicializado / propagado).

Mais uma coisa a ser observada aqui: package doc of math/randstates:

A fonte padrão é segura para uso simultâneo por várias goroutines.

Portanto, a fonte padrão é mais lenta que a Sourceque pode ser obtida por rand.NewSource(), porque a fonte padrão precisa fornecer segurança sob acesso / uso simultâneo, embora rand.NewSource()não ofereça isso (e, portanto Source, é provável que o retorno por ela seja mais rápido).

7. Utilizando strings.Builder

Todas as soluções anteriores retornam um stringcujo conteúdo é construído primeiro em uma fatia ( []runeno Genesis e []bytenas soluções subseqüentes) e depois convertido em string. Essa conversão final precisa fazer uma cópia do conteúdo da fatia, porque os stringvalores são imutáveis ​​e, se a conversão não fizer uma cópia, não é possível garantir que o conteúdo da sequência não seja modificado por meio da fatia original. Para detalhes, consulte Como converter a string utf8 em [] byte? e golang: [] byte (string) vs [] byte (* string) .

Vá 1.10 introduzido strings.Builder. strings.Builderum novo tipo que podemos usar para criar conteúdo stringsemelhante bytes.Buffer. Faz isso internamente usando ae []byte, quando terminarmos, podemos obter o stringvalor final usando seu Builder.String()método. Mas o legal é que ele faz isso sem executar a cópia que acabamos de falar acima. Ousa fazer isso porque a fatia de bytes usada para criar o conteúdo da string não é exposta; portanto, é garantido que ninguém possa modificá-la intencionalmente ou maliciosamente para alterar a string "imutável" produzida.

Portanto, nossa próxima idéia é não criar a sequência aleatória em uma fatia, mas com a ajuda de a strings.Builder, assim que terminarmos, podemos obter e retornar o resultado sem precisar fazer uma cópia dele. Isso pode ajudar em termos de velocidade e definitivamente ajudará em termos de uso e alocações de memória.

func RandStringBytesMaskImprSrcSB(n int) string {
    sb := strings.Builder{}
    sb.Grow(n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            sb.WriteByte(letterBytes[idx])
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return sb.String()
}

Observe que, depois de criar um novo strings.Buidler, chamamos seu Builder.Grow()método, certificando-se de que ele aloca uma fatia interna grande o suficiente (para evitar realocações à medida que adicionamos as letras aleatórias).

8. "Imitando" strings.Buildercom o pacoteunsafe

strings.Builderconstrói a string em um interno []byte, o mesmo que nós mesmos. Então, basicamente, fazê-lo via a strings.Buildertem alguma sobrecarga, a única coisa que optamos strings.Builderpor é evitar a cópia final da fatia.

strings.Builderevita a cópia final usando o pacote unsafe:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

O problema é que também podemos fazer isso sozinhos. Portanto, a idéia aqui é voltar a criar a sequência aleatória em a []byte, mas quando terminar, não a converta stringem retorno, mas faça uma conversão insegura: obtenha uma stringque aponte para a nossa fatia de bytes como dados da sequência .

É assim que isso pode ser feito:

func RandStringBytesMaskImprSrcUnsafe(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&b))
}

(9. Usando rand.Read())

Go 1.7 adicionou uma rand.Read()função e um Rand.Read()método. Devemos ser tentados a usá-los para ler quantos bytes forem necessários em uma única etapa, a fim de obter melhor desempenho.

Há um pequeno "problema" com isso: quantos bytes precisamos? Poderíamos dizer: até o número de letras de saída. Achamos que essa é uma estimativa superior, pois um índice de letras usa menos de 8 bits (1 byte). Mas, neste ponto, já estamos piorando (como obter os bits aleatórios é a "parte mais difícil") e estamos obtendo mais do que o necessário.

Observe também que, para manter uma distribuição igual de todos os índices de letras, pode haver alguns dados aleatórios "lixo" que não poderemos usar; portanto, acabaríamos pulando alguns dados e, assim, ficaríamos curtos quando passarmos por todos os a fatia de bytes. Nós precisaríamos obter mais bytes aleatórios "recursivamente". E agora estamos perdendo a randvantagem da "chamada única para empacotar" ...

Poderíamos "otimizar" o uso dos dados aleatórios dos quais adquirimos math.Rand(). Podemos estimar quantos bytes (bits) precisaremos. 1 letra requer letterIdxBitsbits, e precisamos de nletras; portanto, precisamos de n * letterIdxBits / 8.0bytes arredondados para cima. Podemos calcular a probabilidade de um índice aleatório não ser utilizável (veja acima), para que possamos solicitar mais que "provavelmente" serão suficientes (se isso acontecer, repetimos o processo). Podemos processar a fatia de bytes como um "fluxo de bits", por exemplo, para o qual temos uma boa biblioteca de terceiros: github.com/icza/bitio(divulgação: eu sou o autor).

Mas o código de referência ainda mostra que não estamos ganhando. Por que é tão?

A resposta para a última pergunta é porque rand.Read()usa um loop e continua chamando Source.Int63()até preencher a fatia passada. Exatamente o que a RandStringBytesMaskImprSrc()solução faz, sem o buffer intermediário e sem a complexidade adicionada. É por isso que RandStringBytesMaskImprSrc()permanece no trono. Sim, RandStringBytesMaskImprSrc()usa uma rand.Sourcediferença não sincronizada rand.Read(). Mas o raciocínio ainda se aplica; e que é comprovado se usarmos em Rand.Read()vez de rand.Read()(o primeiro também não é sincronizado).

II Referência

Tudo bem, é hora de comparar as diferentes soluções.

Momento da verdade:

BenchmarkRunes-4                     2000000    723 ns/op   96 B/op   2 allocs/op
BenchmarkBytes-4                     3000000    550 ns/op   32 B/op   2 allocs/op
BenchmarkBytesRmndr-4                3000000    438 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMask-4                 3000000    534 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImpr-4            10000000    176 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrc-4         10000000    139 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrcSB-4       10000000    134 ns/op   16 B/op   1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4   10000000    115 ns/op   16 B/op   1 allocs/op

Apenas mudando de runas para bytes, temos imediatamente um ganho de desempenho de 24% e o requisito de memória cai para um terço .

Livrar-se rand.Intn()e usar em rand.Int63()vez disso dá outro impulso de 20% .

O mascaramento (e a repetição em caso de grandes índices) diminui um pouco (devido a chamadas repetidas): -22% ...

Mas quando usamos todos (ou a maioria) dos 63 bits aleatórios (10 índices de uma rand.Int63()chamada): isso acelera o tempo: 3 vezes .

Se concordarmos com um (não padrão, novo) em rand.Sourcevez de rand.Rand, ganharemos novamente 21%.

Se utilizamos strings.Builder, ganhamos um pequeno 3,5% em velocidade , mas também alcançamos uma redução de 50% no uso e alocações de memória! Isso é bom!

Finalmente, se ousarmos usar o pacote em unsafevez de strings.Builder, obteremos novamente bons 14% .

Comparando a solução final à inicial: RandStringBytesMaskImprSrcUnsafe()é 6,3 vezes mais rápido que RandStringRunes(), usa uma sexta memória e metade do número de alocações . Missão cumprida.


8
@RobbieV Sim, porque um compartilhado rand.Sourceé usado. Uma solução melhor seria passar a rand.Sourcepara a RandStringBytesMaskImprSrc()função e, dessa forma, nenhum bloqueio é necessário e, portanto, o desempenho / eficiência não é afetado. Cada goroutine poderia ter o seu próprio Source.
Icza 14/08/2015

113
@icza, essa é uma das melhores respostas que vi por muito tempo no SO!
astropanic 30/08/2015

1
@ MikeAtlas: Evite usar deferquando for óbvio que você não precisa. Veja grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx

1
@ZanLynx thx pela dica; apesar deferde desbloquear um mutex imediatamente antes ou depois de chamar um bloqueio, é principalmente uma idéia muito boa para a IMO ; você tem a garantia de que não se esqueça de desbloquear, mas também desbloqueie mesmo em uma função intermediária de pânico não fatal.
Mike Atlas

1
@RobbieV, parece que esse código é seguro para threads / goroutine porque a fonte compartilhada subjacente já é um LockedSource que implementa o mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones

130

Você pode apenas escrever um código para ele. Esse código pode ser um pouco mais simples se você quiser confiar que as letras são todos bytes simples quando codificadas em UTF-8.

package main

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

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randSeq(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    fmt.Println(randSeq(10))
}

30
Não se esqueça do rand.Seed (), caso contrário, você obtém a mesma string na primeira vez que inicia ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin

2
A adição de Evan está correta, no entanto, existem outras opções semelhantes: rand.Seed(time.Now().Unix())ourand.Seed(time.Now().UnixNano())
openwonk

7
Para um segredo difícil de adivinhar - uma senha, uma chave criptográfica etc. - nunca use math/rand; use crypto/rand(como a opção 1 de @ Not_A_Golfer).
Twotwotwo

1
@EvanLin Isso não pode ser adivinhado? Se eu tiver que propagar o gerador, o invasor poderá adivinhar a hora em que estou propagando e prever a mesma saída que estou gerando.
Matej

4
Observe que, se você estiver tentando o programa acima com sementes, no playground, ele retornará o mesmo resultado o tempo todo. Eu estava experimentando no playground e depois de algum tempo percebi isso. Funcionou bem caso contrário para mim. Espero que salva alguém vez :)
Gaurav Sinha

18

Use o pacote uniuri , que gera seqüências uniformes (imparciais) criptograficamente seguras.

Disclaimer: Eu sou o autor do pacote


1
Além disso: o autor, dchest, é um excelente desenvolvedor e produziu vários pacotes pequenos e úteis como esse.
Roshambo

16

Duas opções possíveis (pode haver mais, é claro):

  1. Você pode usar o crypto/randpacote que suporta a leitura de matrizes de bytes aleatórios (de / dev / urandom) e é voltado para a geração aleatória criptográfica. consulte http://golang.org/pkg/crypto/rand/#example_Read . Pode ser mais lento que a geração normal de números pseudo-aleatórios.

  2. Pegue um número aleatório e faça o hash usando MD5 ou algo assim.


4

Após icza'suma solução maravilhosamente explicada, aqui está uma modificação dela que usa em crypto/randvez de math/rand.

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

Se você deseja uma solução mais genérica, que permita passar a fatia de bytes de caracteres para criar a cadeia de caracteres, tente usar o seguinte:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

Se você deseja transmitir sua própria fonte de aleatoriedade, seria trivial modificar o acima para aceitar um em io.Readervez de usar crypto/rand.


2

Se você deseja números aleatórios criptograficamente seguros e o conjunto de caracteres exato é flexível (por exemplo, base64 é bom), é possível calcular exatamente qual o tamanho dos caracteres aleatórios necessários a partir do tamanho de saída desejado.

O texto da base 64 é 1/3 maior que a base 256. (proporção 2 ^ 8 vs 2 ^ 6; proporção 8bits / 6bits = 1,333)

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
    rand.Read(buff)
    str := base64.RawURLEncoding.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Nota: você também pode usar RawStdEncoding se preferir + e / caracteres a - e _

Se você deseja hexadecimal, a base 16 é 2x mais longa que a base 256. (proporção 2 ^ 8 vs 2 ^ 4; proporção 8bits / 4bits = 2x)

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

No entanto, você pode estender isso para qualquer conjunto de caracteres arbitrário se tiver um codificador base256 para baseN para o seu conjunto de caracteres. Você pode fazer o mesmo cálculo de tamanho com quantos bits são necessários para representar seu conjunto de caracteres. O cálculo da taxa para qualquer conjunto de caracteres arbitrário é:) ratio = 8 / log2(len(charset)).

Embora essas duas soluções sejam seguras, simples, devem ser rápidas e não desperdiçar seu pool de entropia criptográfica.

Aqui está o playground mostrando que funciona para qualquer tamanho. https://play.golang.org/p/i61WUVR8_3Z


Vale mencionar que Go Playground sempre retorna o mesmo número aleatório, então você não vai ver lá em diferentes seqüências aleatórias em diferentes execuções de que o código
tppz

2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}

Por que gera n * 2 []byte?
M. Rostami

1

Aqui está o meu caminho) Use rand de matemática ou crypto rand como desejar.

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}

0

Se você deseja adicionar alguns caracteres ao seu conjunto de caracteres permitidos, você pode fazer o código funcionar com qualquer coisa que forneça bytes aleatórios por meio de um io.Reader. Aqui estamos usando crypto/rand.

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
    output := make([]byte, n)

    // We will take n bytes, one byte for each character of output.
    randomness := make([]byte, n)

    // read all random
    _, err := rand.Read(randomness)
    if err != nil {
        panic(err)
    }

    // fill output
    for pos := range output {
        // get random item
        random := uint8(randomness[pos])

        // random % 64
        randomPos := random % uint8(len(encodeURL))

        // put into output
        output[pos] = encodeURL[randomPos]
    }

    return output
}

porque é random % 64necessário?
Sung Cho

2
Porque len(encodeURL) == 64. Se random % 64não foi feito, randomPospode ser> = 64 e causar pânico fora dos limites em tempo de execução.
0xcaff

-1
/*
    korzhao
*/

package rand

import (
    crand "crypto/rand"
    "math/rand"
    "sync"
    "time"
    "unsafe"
)

// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

var (
    MRand    = NewRand()
    randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)

// init random number generator
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// get the seed
func getSeed() int64 {
    return time.Now().UnixNano()
}

func (s *Rand) getrand() *rand.Rand {
    return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
    s.Pool.Put(r)
}

// get a random number
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

//  bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
    r := s.getrand()
    defer s.putrand(r)

    return r.Read(p)
}

func CreateRandomString(len int) string {
    b := make([]byte, len)
    _, err := MRand.Read(b)
    if err != nil {
        return ""
    }
    for i := 0; i < len; i++ {
        b[i] = randlist[b[i]%(62)]
    }
    return *(*string)(unsafe.Pointer(&b))
}

24,0 ns / op 16 B / op 1 aloca /


Olá! Bem-vindo ao StackOverflow. Embora você tenha adicionado um trecho de código, sua resposta não inclui nenhum contexto sobre "como funciona" ou "por que é assim que é feito". Lembre-se também de que a pergunta é feita em inglês; portanto, seus comentários também devem ser em inglês.
Cengiz Can

-2
const (
    chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
    charsLen    = len(chars)
    mask        = 1<<6 - 1
)

var rng = rand.NewSource(time.Now().UnixNano())

// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
    /* chars 38个字符
     * rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
     */
    buf := make([]byte, ln)
    for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
        if remain == 0 {
            cache, remain = rng.Int63(), 10
        }
        buf[idx] = chars[int(cache&mask)%charsLen]
        cache >>= 6
        remain--
        idx--
    }
    return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 alocações / op

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.