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?
@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.
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))]}returnstring(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")
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))]}returnstring(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))]}returnstring(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++}}returnstring(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--}returnstring(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--}returnstring(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.
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.
@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!
@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))]}returnstring(b)}
func main(){
rand.Seed(time.Now().UnixNano())
fmt.Println(randSeq(10))}
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.
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):
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.
Pegue um número aleatório e faça o hash usando MD5 ou algo assim.
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++}}returnstring(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 bytevar bitMask bytefor 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 sliceif idx :=int(randomBytes[j%length]& bitMask); idx < availableCharLength {
result[i]= availableCharBytes[idx]
i++}}returnstring(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.
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.
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
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 lenreturn str[:len]}
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 outputfor 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 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 Randstruct{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))}
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.
Respostas:
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.go
e executá-lo comPrefácio :
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:
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:
podemos usar:
Ou melhor ainda:
Agora, isso já é uma grande melhoria: podemos conseguir que seja um
const
(existemstring
constantes, mas não existem constantes de fatia ). Como um ganho extra, a expressãolen(letters)
também será aconst
! (A expressãolen(s)
é constante ses
for uma constante de sequência.)E a que custo? Nada mesmo.
string
s podem ser indexados, o que indexa seus bytes, perfeito, exatamente o que queremos.Nosso próximo destino é assim:
3. Restante
As soluções anteriores obtêm um número aleatório para designar uma letra aleatória chamando
rand.Intn()
quais delegados paraRand.Intn()
quais delegadosRand.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 porlen(letterBytes)
: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 letras52
é muito menor do que1<<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úmeros0..1
com probabilidade dupla do que a partir do intervalo2..5
. Usando 5 bits aleatórios, os números no intervalo0..1
ocorreriam com6/32
probabilidade e os números no intervalo2..5
com5/32
probabilidade 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 porrand.Int63()
. E para manter uma distribuição igual de letras, apenas "aceitamos" o número se ele estiver dentro do intervalo0..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 que0.5
em geral (0.25
em 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 an
repetição, a chance de não termos um bom índice é muito menor do quepow(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:
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 = 10
diferentes índices de letras. Vamos usar todos esses 10: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/rand
pacote que fornece umaRead(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/rand
pacote. Orand.Rand
usa arand.Source
como fonte de bits aleatórios.rand.Source
é uma interface que especifica umInt63() int64
mé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 dorand
pacote), arand.Source
é perfeitamente suficiente para nós:Observe também que esta última solução não exige que você inicialize (propague) o global
Rand
domath/rand
pacote, pois ele não é usado (e nossarand.Source
é adequadamente inicializado / propagado).Mais uma coisa a ser observada aqui: package doc of
math/rand
states:Portanto, a fonte padrão é mais lenta que a
Source
que pode ser obtida porrand.NewSource()
, porque a fonte padrão precisa fornecer segurança sob acesso / uso simultâneo, emborarand.NewSource()
não ofereça isso (e, portantoSource
, é provável que o retorno por ela seja mais rápido).7. Utilizando
strings.Builder
Todas as soluções anteriores retornam um
string
cujo conteúdo é construído primeiro em uma fatia ([]rune
no Genesis e[]byte
nas soluções subseqüentes) e depois convertido emstring
. Essa conversão final precisa fazer uma cópia do conteúdo da fatia, porque osstring
valores 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.Builder
um novo tipo que podemos usar para criar conteúdostring
semelhantebytes.Buffer
. Faz isso internamente usando ae[]byte
, quando terminarmos, podemos obter ostring
valor final usando seuBuilder.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.Observe que, depois de criar um novo
strings.Buidler
, chamamos seuBuilder.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.Builder
com o pacoteunsafe
strings.Builder
constrói a string em um interno[]byte
, o mesmo que nós mesmos. Então, basicamente, fazê-lo via astrings.Builder
tem alguma sobrecarga, a única coisa que optamosstrings.Builder
por é evitar a cópia final da fatia.strings.Builder
evita a cópia final usando o pacoteunsafe
: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 convertastring
em retorno, mas faça uma conversão insegura: obtenha umastring
que aponte para a nossa fatia de bytes como dados da sequência .É assim que isso pode ser feito:
(9. Usando
rand.Read()
)Go 1.7 adicionou uma
rand.Read()
função e umRand.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
rand
vantagem 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 requerletterIdxBits
bits, e precisamos den
letras; portanto, precisamos den * letterIdxBits / 8.0
bytes 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 chamandoSource.Int63()
até preencher a fatia passada. Exatamente o que aRandStringBytesMaskImprSrc()
solução faz, sem o buffer intermediário e sem a complexidade adicionada. É por isso queRandStringBytesMaskImprSrc()
permanece no trono. Sim,RandStringBytesMaskImprSrc()
usa umarand.Source
diferença não sincronizadarand.Read()
. Mas o raciocínio ainda se aplica; e que é comprovado se usarmos emRand.Read()
vez derand.Read()
(o primeiro também não é sincronizado).II Referência
Tudo bem, é hora de comparar as diferentes soluções.
Momento da verdade:
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 emrand.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.Source
vez derand.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
unsafe
vez destrings.Builder
, obteremos novamente bons 14% .Comparando a solução final à inicial:
RandStringBytesMaskImprSrcUnsafe()
é 6,3 vezes mais rápido queRandStringRunes()
, usa uma sexta memória e metade do número de alocações . Missão cumprida.fonte
rand.Source
é usado. Uma solução melhor seria passar arand.Source
para aRandStringBytesMaskImprSrc()
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óprioSource
.defer
quando for óbvio que você não precisa. Veja grokbase.com/t/gg/golang-nuts/158zz5p42w/…defer
de 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.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.
fonte
rand.Seed(time.Now().Unix())
ourand.Seed(time.Now().UnixNano())
math/rand
; usecrypto/rand
(como a opção 1 de @ Not_A_Golfer).Use o pacote uniuri , que gera seqüências uniformes (imparciais) criptograficamente seguras.
Disclaimer: Eu sou o autor do pacote
fonte
Duas opções possíveis (pode haver mais, é claro):
Você pode usar o
crypto/rand
pacote 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.Pegue um número aleatório e faça o hash usando MD5 ou algo assim.
fonte
Após
icza's
uma solução maravilhosamente explicada, aqui está uma modificação dela que usa emcrypto/rand
vez demath/rand
.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:
Se você deseja transmitir sua própria fonte de aleatoriedade, seria trivial modificar o acima para aceitar um em
io.Reader
vez de usarcrypto/rand
.fonte
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)
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)
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
fonte
fonte
[]byte
?Aqui está o meu caminho) Use rand de matemática ou crypto rand como desejar.
fonte
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
.fonte
random % 64
necessário?len(encodeURL) == 64
. Serandom % 64
não foi feito,randomPos
pode ser> = 64 e causar pânico fora dos limites em tempo de execução.24,0 ns / op 16 B / op 1 aloca /
fonte
BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 alocações / op
fonte