मुझे गो में केवल अक्षरों (अपरकेस या लोअरकेस) का एक यादृच्छिक स्ट्रिंग चाहिए, कोई संख्या नहीं। ऐसा करने का सबसे तेज़ और सरल तरीका क्या है?
मुझे गो में केवल अक्षरों (अपरकेस या लोअरकेस) का एक यादृच्छिक स्ट्रिंग चाहिए, कोई संख्या नहीं। ऐसा करने का सबसे तेज़ और सरल तरीका क्या है?
जवाबों:
पॉल का समाधान एक सरल , सामान्य समाधान प्रदान करता है ।
सवाल "सबसे तेज और सरल तरीका" पूछता है । सबसे तेज़ भाग को भी संबोधित करते हैं। हम अपने अंतिम, सबसे तेज़ कोड में एक पुनरावृत्त तरीके से पहुंचेंगे। प्रत्येक चलना बेंचमार्क जवाब के अंत में पाया जा सकता है।
सभी समाधान और बेंचमार्किंग कोड गो प्लेग्राउंड पर देखे जा सकते हैं । खेल के मैदान पर कोड एक परीक्षण फ़ाइल है, एक निष्पादन योग्य नहीं है। आपको इसे नाम की एक फ़ाइल में सहेजना होगा XX_test.go
और इसे साथ चलाना होगा
go test -bench . -benchmem
प्राक्कथन :
सबसे तेज़ समाधान एक जाने-योग्य समाधान नहीं है यदि आपको बस एक यादृच्छिक स्ट्रिंग की आवश्यकता है। उसके लिए, पॉल का समाधान एकदम सही है। यह है अगर प्रदर्शन मायने रखता है। हालाँकि पहले 2 चरण ( बाइट्स और रेमिनेडर ) एक स्वीकार्य समझौता हो सकता है: वे 50% की तरह प्रदर्शन में सुधार करते हैं ( II। बेंचमार्क अनुभाग में सटीक संख्या देखें ), और वे जटिलता को बहुत अधिक नहीं बढ़ाते हैं।
कहा जाता है कि, भले ही आपको सबसे तेज़ समाधान की आवश्यकता न हो, इस उत्तर के माध्यम से पढ़ना साहसिक और शैक्षिक हो सकता है।
एक अनुस्मारक के रूप में, मूल, सामान्य समाधान जो हम सुधार रहे हैं वह यह है:
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)
}
यदि अक्षरों को चुनने और यादृच्छिक स्ट्रिंग को इकट्ठा करने के लिए केवल अंग्रेजी वर्णमाला के अपरकेस और लोअरकेस अक्षर होते हैं, तो हम केवल बाइट्स के साथ काम कर सकते हैं क्योंकि UTF-8 एन्कोडिंग में अंग्रेजी वर्णमाला पत्र 1 से 1 बाइट्स के लिए मैप करता है (जो यह है कि गो स्टोर स्ट्रिंग्स)।
इसलिए इसके बजाय:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
हम प्रयोग कर सकते हैं:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
या इससे भी बेहतर:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
अब यह पहले से ही एक बड़ा सुधार है: हम इसे एक होने के लिए प्राप्त कर सकते const
हैं ( string
स्थिरांक हैं लेकिन कोई स्लाइस स्थिरांक नहीं हैं )। अतिरिक्त लाभ के रूप में, अभिव्यक्ति len(letters)
भी एक होगी const
! ( len(s)
यदि s
स्ट्रिंग स्थिर है तो अभिव्यक्ति स्थिर है।)
और किस कीमत पर? कुछ भी नहीं। string
s को अनुक्रमित किया जा सकता है जो अपने बाइट्स को अनुक्रमित करता है, एकदम सही, जो हम चाहते हैं।
हमारा अगला गंतव्य इस तरह दिखता है:
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)
}
पिछले समाधानों को एक यादृच्छिक पत्र प्राप्त करने के लिए एक यादृच्छिक संख्या प्राप्त होती है, जिसे कॉल करके rand.Intn()
प्रतिनिधियों को भेजा जाता Rand.Intn()
है Rand.Int31n()
।
यह तुलना में बहुत धीमा है rand.Int63()
जो 63 यादृच्छिक बिट्स के साथ एक यादृच्छिक संख्या का उत्पादन करता है।
तो हम बस rand.Int63()
को विभाजित करके शेष को कॉल और उपयोग कर सकते हैं 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)
}
यह काम करता है और काफी तेज है, नुकसान यह है कि सभी अक्षरों की संभावना बिल्कुल समान नहीं होगी (यह मानते हुए rand.Int63()
सभी 63-बिट संख्याओं को समान संभावना के साथ पैदा करता है)। हालाँकि विकृति अत्यंत छोटी है क्योंकि अक्षरों की संख्या 52
इससे बहुत छोटी है 1<<63 - 1
, इसलिए व्यवहार में यह बिल्कुल ठीक है।
इसे समझने में आसान बनाने के लिए: मान लीजिए कि आप श्रेणी में एक यादृच्छिक संख्या चाहते हैं 0..5
। 3 यादृच्छिक बिट्स का उपयोग करना, यह संख्या 0..1
को सीमा से दोगुनी संभावना के साथ उत्पन्न करेगा 2..5
। 5 यादृच्छिक बिट्स का उपयोग करते हुए, रेंज में संख्याएँ 0..1
होती हैं6/32
संभाव्यता के होती हैं और संभावना के 2..5
साथ सीमा में संख्याएँ 5/32
जो अब वांछित के करीब है। बिट्स की संख्या में वृद्धि करना इसे कम महत्वपूर्ण बनाता है, जब 63 बिट्स तक पहुंच जाता है, तो यह नगण्य है।
पिछले समाधान पर बिल्डिंग, हम अक्षरों की संख्या का प्रतिनिधित्व करने के लिए आवश्यक यादृच्छिक संख्या के सबसे कम बिट्स का उपयोग करके केवल अक्षरों के समान वितरण को बनाए रख सकते हैं। इसलिए उदाहरण के लिए अगर हमारे पास 52 अक्षर हैं, तो इसे प्रस्तुत करने के लिए 6 बिट्स की आवश्यकता होती है 52 = 110100b
:। इसलिए हम केवल सबसे कम 6 बिट्स का उपयोग करेंगे जो कि लौटाए गए नंबर से होगाrand.Int63()
। और अक्षरों के समान वितरण को बनाए रखने के लिए, हम केवल "संख्या" को स्वीकार करते हैं यदि यह सीमा में आता है 0..len(letterBytes)-1
। यदि सबसे कम बिट्स अधिक हैं, तो हम इसे त्याग देते हैं और एक नया यादृच्छिक संख्या क्वेरी करते हैं।
ध्यान दें कि सबसे कम बिट्स की संभावना सामान्य या औसत len(letterBytes)
से कम से अधिक होने की संभावना है , जिसका अर्थ है कि भले ही यह मामला होगा, इस "दुर्लभ" मामले को दोहराते हुए एक अच्छा नहीं मिलने की संभावना कम हो जाती है नंबर। पुनरावृत्ति के बाद , मौका है कि हम एक अच्छा सूचकांक नहीं है की तुलना में बहुत कम है , और यह सिर्फ एक ऊपरी आकलन है। 52 अक्षरों के मामले में मौका है कि 6 सबसे कम बिट अच्छे नहीं हैं ; उदाहरण के लिए जिसका अर्थ है कि 10 पुनरावृत्ति के बाद एक अच्छी संख्या नहीं होने की संभावना है ।0.5
0.25
n
pow(0.5, n)
(64-52)/64 = 0.19
1e-8
तो यहाँ समाधान है:
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)
}
पिछला समाधान केवल 63 यादृच्छिक बिट्स द्वारा सबसे कम 6 बिट्स का उपयोग करता है rand.Int63()
। यह एक बेकार है क्योंकि यादृच्छिक बिट्स हमारे एल्गोरिथ्म का सबसे धीमा हिस्सा है।
यदि हमारे पास ५२ अक्षर हैं, तो इसका अर्थ है कि ६ बिट्स एक अक्षर सूचकांक है। तो 63 यादृच्छिक बिट्स 63/6 = 10
अलग-अलग पत्र सूचकांकों को नामित कर सकते हैं । चलो उन सभी का उपयोग करें 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)
}
मास्किंग सुधार , बहुत अच्छी है बहुत ज्यादा नहीं हम इस पर सुधार कर सकते हैं। हम कर सकते हैं, लेकिन जटिलता के लायक नहीं।
अब हम कुछ और सुधारने की कोशिश करते हैं। यादृच्छिक संख्याओं का स्रोत।
एक crypto/rand
पैकेज है जो एक Read(b []byte)
फ़ंक्शन प्रदान करता है , इसलिए हम इसका उपयोग कर सकते हैं कि हमें जितनी आवश्यकता हो उतने कॉल के साथ कई बाइट मिलें। यह प्रदर्शन के मामले में मदद नहीं करेगाcrypto/rand
एक क्रिप्टोग्राफिक रूप से सुरक्षित छद्म आयामी संख्या जनरेटर लागू करता है इसलिए यह बहुत धीमा है।
तो चलो math/rand
पैकेज से चिपके रहते हैं । rand.Rand
एक का उपयोग करता है rand.Source
यादृच्छिक बिट्स के स्रोत के रूप में। rand.Source
एक इंटरफ़ेस है जो एक Int63() int64
विधि को निर्दिष्ट करता है: वास्तव में और केवल वही चीज जो हमें चाहिए और हमारे नवीनतम समाधान में उपयोग की जाती है।
इसलिए हमें वास्तव में rand.Rand
(या तो स्पष्ट या वैश्विक, rand
पैकेज में से एक साझा ) की आवश्यकता नहीं है, एक 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 < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
इसके अलावा ध्यान दें कि यह पिछले समाधान (बीज) प्रारंभ करने में वैश्विक की आवश्यकता नहीं है Rand
के math/rand
पैकेज के रूप में है कि नहीं किया जाता है (और हमारे rand.Source
ठीक से प्रारंभ / वरीयता प्राप्त है)।
यहां एक और बात ध्यान देने योग्य है: math/rand
राज्यों का पैकेज डॉक्टर :
डिफ़ॉल्ट स्रोत कई गोरोनाइट द्वारा समवर्ती उपयोग के लिए सुरक्षित है।
इसलिए डिफ़ॉल्ट स्रोत इसके Source
द्वारा प्राप्त किया जा सकता है की तुलना में धीमा है rand.NewSource()
, क्योंकि डिफ़ॉल्ट स्रोत को समवर्ती उपयोग / उपयोग के तहत सुरक्षा प्रदान करना है, जबकि rand.NewSource()
यह पेशकश नहीं करता है (और इस प्रकार इसके Source
द्वारा लौटाए जाने की संभावना अधिक तेज़ है)।
strings.Builder
सभी पिछले समाधान वापसी एक string
जिनकी सामग्री पहले एक टुकड़ा में बनाया गया है ( []rune
में उत्पत्ति , और []byte
बाद के समाधान में), और फिर करने के लिए परिवर्तित string
। इस अंतिम रूपांतरण को स्लाइस की सामग्री की एक प्रति string
बनानी होगी , क्योंकि मान अपरिवर्तनीय हैं, और यदि रूपांतरण प्रतिलिपि नहीं बनाएगा, तो यह गारंटी नहीं दी जा सकती है कि स्ट्रिंग की सामग्री को उसके मूल स्लाइस के माध्यम से संशोधित नहीं किया गया है। विवरण के लिए, देखें कि utf8 स्ट्रिंग को [] बाइट में कैसे परिवर्तित किया जाए? और गोलंग: [] बाइट (स्ट्रिंग) बनाम [] बाइट (* स्ट्रिंग) ।
जाओ 1.10 पेश किया strings.Builder
। strings.Builder
एक नए प्रकार का उपयोग हम string
इसी तरह की सामग्री बनाने के लिए कर सकते हैं bytes.Buffer
। यह आंतरिक रूप से इसका उपयोग करता है []byte
, और जब हम कर लेते हैं, तो हम string
इसकी Builder.String()
विधि का उपयोग करके अंतिम मूल्य प्राप्त कर सकते हैं । लेकिन इसमें जो अच्छा है वह यह है कि यह प्रतिलिपि के बिना हम ऊपर की बात करते हैं। यह ऐसा करने की हिम्मत करता है क्योंकि स्ट्रिंग की सामग्री के निर्माण के लिए उपयोग की जाने वाली बाइट स्लाइस उजागर नहीं होती है, इसलिए यह गारंटी दी जाती है कि कोई भी इसे उत्पादित "अपरिवर्तनीय" स्ट्रिंग को बदलने के लिए अनजाने या दुर्भावना से संशोधित नहीं कर सकता है।
तो हमारा अगला विचार एक स्लाइस में यादृच्छिक स्ट्रिंग का निर्माण नहीं करना है, लेकिन एक की मदद से strings.Builder
, इसलिए एक बार जब हम काम कर लेते हैं, तो हम इसकी प्रतिलिपि बनाने के बिना परिणाम प्राप्त कर सकते हैं और वापस कर सकते हैं। यह गति के संदर्भ में मदद कर सकता है, और यह निश्चित रूप से स्मृति उपयोग और आवंटन के संदर्भ में मदद करेगा।
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()
}
ध्यान दें कि एक नया बनाने के बाद strings.Buidler
, हमने इसकी Builder.Grow()
विधि को बुलाया , यह सुनिश्चित करते हुए कि यह एक बड़े-पर्याप्त आंतरिक स्लाइस (वास्तविक अक्षरों को जोड़ने से बचने के लिए) को आवंटित करता है।
strings.Builder
पैकेज के साथ "नकल"unsafe
strings.Builder
एक आंतरिक में स्ट्रिंग का निर्माण करता है []byte
, जैसा कि हमने खुद किया था। तो मूल रूप से strings.Builder
कुछ ओवरहेड के माध्यम से कर रहा है , केवल एक चीज जिसे हमने स्विच किया strings.Builder
है वह स्लाइस की अंतिम प्रतिलिपि से बचने के लिए है।
strings.Builder
पैकेज का उपयोग करके अंतिम प्रतिलिपि से बचता है unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
बात यह है, हम खुद भी ऐसा कर सकते हैं। इसलिए यहाँ पर विचार यादृच्छिक स्ट्रिंग के निर्माण में वापस जाने के लिए है []byte
, लेकिन जब हम काम कर रहे होते हैं, तो इसे string
वापस लौटने के लिए परिवर्तित नहीं करते हैं, लेकिन एक असुरक्षित रूपांतरण करते हैं: string
जो स्ट्रिंग डेटा के रूप में हमारे बाइट स्लाइस को इंगित करता है। ।
यह इस प्रकार किया जा सकता है:
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))
}
rand.Read()
)जाओ 1.7 ने एक rand.Read()
फ़ंक्शन और एक Rand.Read()
विधि जोड़ी । बेहतर प्रदर्शन को प्राप्त करने के लिए हमें एक कदम में जितनी बाइट चाहिए उतने बाइट्स पढ़ने के लिए इनका उपयोग करने का लालच दिया जाना चाहिए।
इसके साथ एक छोटी "समस्या" है: हमें कितने बाइट्स की आवश्यकता है? हम कह सकते हैं: आउटपुट पत्रों की संख्या जितनी। हमें लगता है कि यह एक ऊपरी अनुमान है, क्योंकि एक पत्र सूचकांक 8 बिट्स (1 बाइट) से कम का उपयोग करता है। लेकिन इस बिंदु पर हम पहले से ही खराब कर रहे हैं (जैसा कि यादृच्छिक बिट्स "हार्ड पार्ट" है), और हम आवश्यकता से अधिक प्राप्त कर रहे हैं।
यह भी ध्यान दें कि सभी पत्र सूचकांकों के समान वितरण को बनाए रखने के लिए, कुछ "कचरा" यादृच्छिक डेटा हो सकता है, जिसका हम उपयोग नहीं कर पाएंगे, इसलिए हम कुछ डेटा को छोड़ देंगे, और इस तरह जब हम सभी के माध्यम से कम हो जाएंगे बाइट स्लाइस। हमें "पुनरावर्ती" अधिक यादृच्छिक बाइट्स प्राप्त करने की आवश्यकता होगी। और अब हम "सिंगल कॉल टू rand
पैकेज" लाभ भी खो रहे हैं ...
हमारे द्वारा प्राप्त रैंडम डेटा के उपयोग को हम "कुछ हद तक" अनुकूलित कर सकते हैं math.Rand()
। हम अनुमान लगा सकते हैं कि हमें कितने बाइट्स (बिट्स) की आवश्यकता होगी। 1 पत्र को letterIdxBits
बिट्स की आवश्यकता होती है, और हमें n
अक्षरों की आवश्यकता होती है , इसलिए हमें n * letterIdxBits / 8.0
बाइट्स को गोल करना होगा। हम एक यादृच्छिक सूचकांक के प्रयोग योग्य नहीं होने की संभावना की गणना कर सकते हैं (ऊपर देखें), इसलिए हम अधिक अनुरोध कर सकते हैं कि "अधिक संभावना" पर्याप्त होगी (यदि यह पता चला कि यह नहीं है, तो हम प्रक्रिया को दोहराते हैं)। हम उदाहरण के लिए बाइट स्लाइस को "बिट स्ट्रीम" के रूप में संसाधित कर सकते हैं, जिसके लिए हमारे पास एक अच्छा 3rd पार्टी लिब है: github.com/icza/bitio
(प्रकटीकरण: मैं लेखक हूं)।
लेकिन बेंचमार्क कोड अभी भी दिखाता है कि हम जीत नहीं रहे हैं। ऐसा क्यों है?
अंतिम प्रश्न का उत्तर है क्योंकि rand.Read()
लूप का उपयोग करता है और Source.Int63()
तब तक कॉल करता रहता है जब तक कि यह पास के स्लाइस को भर नहीं देता है। वास्तव में क्या RandStringBytesMaskImprSrc()
समाधान करता है, बिना मध्यवर्ती बफर, और जटिलता के बिना। इसलिए RandStringBytesMaskImprSrc()
सिंहासन पर बने हुए हैं। हां, इसके विपरीत RandStringBytesMaskImprSrc()
एक असंबद्ध का उपयोग करता है । लेकिन तर्क अभी भी लागू होता है; और जो सिद्ध होता है यदि हम इसके बजाय प्रयोग करते हैं (पूर्व भी अनसंकटेड है)।rand.Source
rand.Read()
Rand.Read()
rand.Read()
सब ठीक है, यह विभिन्न समाधानों को बेंचमार्क करने का समय है।
सच्चाई का क्षण:
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
बस रन से बाइट्स पर स्विच करने से, हमारे पास तुरंत 24% प्रदर्शन लाभ होता है, और मेमोरी की आवश्यकता एक तिहाई तक गिर जाती है ।
इसके बजाय छुटकारा पाने rand.Intn()
और उपयोग rand.Int63()
करने से एक और 20% बढ़ावा मिलता है।
मास्किंग (और बड़े सूचकांकों के मामले में) थोड़ा धीमा हो जाता है (पुनरावृत्ति कॉल के कारण): -22% ...
लेकिन जब हम 63 यादृच्छिक बिट्स (एक rand.Int63()
कॉल से 10 सूचकांक) के सभी (या अधिकांश) का उपयोग करते हैं : जो कि बड़े समय को गति देता है: 3 बार ।
यदि हम rand.Source
इसके बजाय (गैर-डिफ़ॉल्ट, नया) के साथ समझौता करते हैं rand.Rand
, तो हम फिर से 21% प्राप्त करते हैं ।
यदि हम उपयोग करते हैं strings.Builder
, तो हम गति में 3.5% की वृद्धि करते हैं , लेकिन हमने मेमोरी उपयोग और आवंटन में 50% की कमी भी हासिल की है ! यह अच्छा है!
अंत में अगर हम unsafe
इसके बजाय पैकेज का उपयोग करने की हिम्मत करते हैं strings.Builder
, तो हम फिर से एक अच्छा 14% हासिल करते हैं ।
प्रारंभिक समाधान करने के लिए अंतिम तुलना: RandStringBytesMaskImprSrcUnsafe()
है 6.3 गुना तेजी से RandStringRunes()
, का उपयोग करता छठे स्मृति और कुछ आवंटन के रूप में आधा । मिशन पूरा हुआ।
rand.Source
का उपयोग किया जाता है। एक बेहतर वर्कअराउंड फ़ंक्शन को पास rand.Source
करने के लिए होगा RandStringBytesMaskImprSrc()
, और इस तरह कोई लॉकिंग की आवश्यकता नहीं है और इसलिए प्रदर्शन / दक्षता प्रभावित नहीं होती है। प्रत्येक गोरोइन का अपना हो सकता है Source
।
defer
जब यह स्पष्ट हो कि आपको इसकी आवश्यकता नहीं है तो इसका उपयोग करने से बचना चाहिए । देखें grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
किसी लॉक को कॉल करने से पहले या बाद में म्यूटेक्स को अनलॉक करने के लिए आईएमओ ज्यादातर एक बहुत अच्छा विचार है; आप दोनों को अनलॉक न करने की गारंटी देते हैं, बल्कि एक गैर-घातक आतंक मध्य-समारोह में भी अनलॉक करने की गारंटी देते हैं।
आप इसके लिए सिर्फ कोड लिख सकते हैं। यह कोड थोड़ा सरल हो सकता है यदि आप 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))
}
rand.Seed(time.Now().Unix())
याrand.Seed(time.Now().UnixNano())
math/rand
; उपयोग crypto/rand
(जैसे @ Not_A_Golfer का विकल्प 1)।
दो संभावित विकल्प (निश्चित रूप से अधिक हो सकते हैं):
आप उस crypto/rand
पैकेज का उपयोग कर सकते हैं जो रैंडम बाइट एरेज़ (/ / dev / urandom) को पढ़ने का समर्थन करता है और क्रिप्टोग्राफ़िक रैंडम जेनरेशन की ओर तैयार है। http://golang.org/pkg/crypto/rand/#example_Read देखें । यह हालांकि सामान्य छद्म यादृच्छिक संख्या पीढ़ी की तुलना में धीमा हो सकता है।
एक यादृच्छिक संख्या लें और इसे md5 या कुछ इस तरह से उपयोग करें।
icza's
आश्चर्यजनक रूप से समझाए गए समाधान के बाद , यहां इसका एक संशोधन है जो crypto/rand
इसके बजाय उपयोग करता है 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
}
यदि आप अधिक सामान्य समाधान चाहते हैं, जिससे आप स्ट्रिंग को बनाने के लिए कैरेक्टर बाइट्स के स्लाइस में पास हो सकते हैं, तो आप इसका उपयोग करने का प्रयास कर सकते हैं:
// 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)
}
यदि आप यादृच्छिकता के अपने स्रोत में पारित करना चाहते हैं, तो io.Reader
उपयोग के बजाय स्वीकार करने के लिए उपरोक्त को संशोधित करना तुच्छ होगा crypto/rand
।
यदि आप क्रिप्टोग्राफिक रूप से सुरक्षित रैंडम नंबर चाहते हैं , और सटीक चारसेट लचीला है (मान लीजिए, बेस 64 ठीक है), तो आप गणना कर सकते हैं कि वांछित आउटपुट आकार से आपको यादृच्छिक वर्णों की लंबाई कितनी चाहिए।
बेस 64 टेक्स्ट बेस 256 से 1/3 लंबा है। (2 ^ 8 बनाम 2 ^ 6; 8 बिट्स / 6 बिट्स = 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
}
नोट: यदि आप पसंद करते हैं तो आप RawStdEncoding का उपयोग कर सकते हैं - और / और - और अक्षर
यदि आप हेक्स चाहते हैं, बेस 16 बेस बेस 256 की तुलना में 2 गुना लंबा है। (2 ^ 8 बनाम 2 ^ 4; 8 बिट्स / 4 बिट्स: 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
}
हालाँकि, आप इसे किसी भी मनमाने चरित्र सेट पर विस्तारित कर सकते हैं यदि आपके पास अपने चरित्र सेट के लिए बेस एनएन एनकोडर है। आप अपने चरित्र सेट का प्रतिनिधित्व करने के लिए कितने बिट्स आवश्यक हैं, उसी आकार की गणना कर सकते हैं। किसी भी मनमाने चारसेट के लिए अनुपात गणना है:ratio = 8 / log2(len(charset))
।
हालांकि ये दोनों समाधान सुरक्षित हैं, सरल हैं, तेज होना चाहिए, और अपने क्रिप्टो एन्ट्रापी पूल को बर्बाद न करें।
यहाँ खेल का मैदान दिखा यह किसी भी आकार के लिए काम करता है। https://play.golang.org/p/i61WUVR8_3Z
func Rand(n int) (str string) {
b := make([]byte, n)
rand.Read(b)
str = fmt.Sprintf("%x", b)
return
}
[]byte
?
यदि आप अनुमत वर्णों के अपने पूल में कुछ वर्ण जोड़ने के लिए तैयार हैं, तो आप कुछ भी के साथ कोड काम कर सकते हैं जो एक io.Reader के माध्यम से यादृच्छिक बाइट्स प्रदान करता है। यहां हम उपयोग कर रहे हैं 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
}
random % 64
जरूरी है?
len(encodeURL) == 64
। अगर random % 64
ऐसा नहीं किया गया था, तो randomPos
रनिंग के समय घबराहट होने की संभावना है।
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))
}
बेंचमार्करंडस्ट्र 16-8 20000000 68.1 ns / op 16 B / op 1 ऑलोकस / ऑप