Мне нужна только случайная строка символов (прописных или строчных букв) ), без цифр, в Go. Какой самый быстрый и простой способ сделать это?
Вопрос ищет «самый быстрый и простой» подход. Ответ Пола предлагает простой метод. Однако давайте также рассмотрим «самый быстрый» аспект. Мы будем итеративно совершенствовать наш код, придя к оптимизированному решению.
1. Бытие (Руны)
Первоначальное решение, которое мы оптимизируем:
import (
"math/rand"
"time"
)
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. Байты
Если символы, используемые для случайной строки, ограничены прописными и строчными буквами английского алфавита, мы можем работать с байтами, поскольку буквы английского алфавита сопоставляются 1-к-1 с байтами в кодировке UTF-8 ( который Go использует для хранения строк).
Таким образом, мы можем заменить:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
with:
var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Или еще лучше:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Это значительное улучшение, поскольку теперь мы можем использовать константу (Go поддерживает строковые константы, но не константы среза). Кроме того, выражение len(letters) также будет константой.
3. Remainder
Предыдущие решения определяли случайное число для буквы путем вызова rand.Intn() (который делегирует функции Rand.Intn() и далее Rand.Int31n()).
Это медленнее, чем использование rand.Int63(), которое генерирует случайное число из 63 случайных битов.
Так что мы можем просто вызвать rand.Int63() и используйте остаток от деления на len(letters):
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Int63() % int64(len(letters))]
}
return string(b)
}
Это быстрее, при сохранении равного распределения вероятностей всех букв (хотя искажение незначительно, количество букв 52 намного меньше, чем 1
4. Маскирование
Мы можем поддерживать равное распределение букв, используя только младшие биты случайного числа, достаточные для представления количества букв. Для 52 букв требуется 6 бит: 52 = 110100b. Поэтому мы будем использовать только младшие 6 бит числа, возвращаемого функцией rand.Int63().
Мы также «принимаем» число только в том случае, если оно попадает в диапазон 0..len(letterBytes)-1. . Если младшие биты больше, мы отбрасываем и запрашиваем новое число.
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 15. Улучшено маскирование
Предыдущее решение использует только младшие 6 бит из 63 случайных битов из rand.Int63(). Это неэффективно, поскольку получение случайных битов — самая медленная часть нашего алгоритма.
Поскольку у нас 52 буквы, 6 бит кодируют буквенный индекс. 63 случайных бита могут обозначать 63/6 = 10 различных буквенных индексов. Давайте использовать все 10:
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx >= letterIdxBits
remain--
}
return string(b)
}
6. Источник
Улучшенная маскировка весьма эффективна. Давайте рассмотрим другой аспект: источник случайных чисел.
Пакет crypto/rand предоставляет функцию Read(b []byte). Однако это не повысит производительность, поскольку crypto/rand реализует криптографически безопасный генератор псевдослучайных чисел, который работает медленнее.
Поэтому мы будем придерживаться пакета math/rand. rand.Rand использует rand.Source в качестве источника случайных битов. Таким образом, мы можем напрямую использовать rand.Source:
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 >= letterIdxBits
remain--
}
return string(b)
}
7. Использование strings.Builder
Предыдущие решения возвращали строки, созданные сначала в срезе ([]руна в Бытие и []байт впоследствии), а затем преобразованные в строку. Это окончательное преобразование требует копирования содержимого фрагмента, поскольку строковые значения неизменяемы.
В Go 1.10 появились strings.Builder. Этот новый тип можно использовать для создания строкового содержимого аналогично bytes.Buffer. Он использует []байт внутри себя и не требует копирования содержимого для создания строки.
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 >= letterIdxBits
remain--
}
return sb.String()
}
8. «Имитация» строк.Builder с пакетом unsafe
strings.Builder строит строку во внутреннем []байте, как и мы сами. Таким образом, использование strings.Builder приводит к некоторым накладным расходам, которые мы исключили, чтобы избежать окончательного копирования.
Однако мы также можем избежать этого копирования, используя unsafe пакета:
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
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3