गो में एक निश्चित लंबाई का एक यादृच्छिक स्ट्रिंग कैसे उत्पन्न करें?


300

मुझे गो में केवल अक्षरों (अपरकेस या लोअरकेस) का एक यादृच्छिक स्ट्रिंग चाहिए, कोई संख्या नहीं। ऐसा करने का सबसे तेज़ और सरल तरीका क्या है?


2
@VinceEmigh: यहाँ बुनियादी सवालों पर चर्चा करने वाला एक मेटा विषय है। meta.stackoverflow.com/q/274645/395461 व्यक्तिगत रूप से, मुझे लगता है कि मूल प्रश्न ठीक हैं यदि अच्छी तरह से लिखा गया है और ऑन-टॉपिक हैं। नीचे दिए गए उत्तरों को देखें, वे उन चीजों का एक गुच्छा चित्रित करते हैं जो किसी नए व्यक्ति के लिए जाने के लिए उपयोगी होंगे। लूप्स के लिए, कास्टिंग, मेक (), आदि
शैनन मैथ्यूज

2
@ शान्नोन " यह प्रश्न किसी भी शोध प्रयास को प्रदर्शित नहीं करता है " (आपके लिंक में पहला अत्यधिक उत्कीर्ण उत्तर) - यही वह है जिसका मैं उल्लेख कर रहा था। वह कोई शोध प्रयास नहीं दिखाता है। कोई प्रयास नहीं (एक प्रयास, या यहां तक ​​कि यह बताते हुए कि वह ऑनलाइन देखा, जो उसने स्पष्ट रूप से नहीं देखा है)। हालांकि यह किसी नए व्यक्ति के लिए उपयोगी होगा , यह साइट नए लोगों को पढ़ाने पर केंद्रित नहीं है। यह विशिष्ट प्रोग्रामिंग समस्याओं / सवालों के जवाब देने पर केंद्रित है, न कि ट्यूटोरियल / गाइड के लिए। यद्यपि इसका उपयोग उत्तरार्द्ध के लिए किया जा सकता है, यह ध्यान केंद्रित नहीं है, और इस प्रकार यह प्रश्न बंद होना चाहिए। इसके बजाय, इसके चम्मच /:
विंस

9
@VinceEmigh मैंने यह सवाल एक साल पहले पूछा था। मैंने यादृच्छिक स्ट्रिंग्स के लिए ऑनलाइन खोज की थी और डॉक्स भी पढ़े थे। लेकिन यह मददगार नहीं था। यदि मैंने प्रश्न में नहीं लिखा है, तो इसका मतलब यह नहीं है कि मैंने शोध नहीं किया है।
अनीश शाह

जवाबों:


808

पॉल का समाधान एक सरल , सामान्य समाधान प्रदान करता है ।

सवाल "सबसे तेज और सरल तरीका" पूछता है । सबसे तेज़ भाग को भी संबोधित करते हैं। हम अपने अंतिम, सबसे तेज़ कोड में एक पुनरावृत्त तरीके से पहुंचेंगे। प्रत्येक चलना बेंचमार्क जवाब के अंत में पाया जा सकता है।

सभी समाधान और बेंचमार्किंग कोड गो प्लेग्राउंड पर देखे जा सकते हैं । खेल के मैदान पर कोड एक परीक्षण फ़ाइल है, एक निष्पादन योग्य नहीं है। आपको इसे नाम की एक फ़ाइल में सहेजना होगा XX_test.goऔर इसे साथ चलाना होगा

go test -bench . -benchmem

प्राक्कथन :

सबसे तेज़ समाधान एक जाने-योग्य समाधान नहीं है यदि आपको बस एक यादृच्छिक स्ट्रिंग की आवश्यकता है। उसके लिए, पॉल का समाधान एकदम सही है। यह है अगर प्रदर्शन मायने रखता है। हालाँकि पहले 2 चरण ( बाइट्स और रेमिनेडर ) एक स्वीकार्य समझौता हो सकता है: वे 50% की तरह प्रदर्शन में सुधार करते हैं ( II। बेंचमार्क अनुभाग में सटीक संख्या देखें ), और वे जटिलता को बहुत अधिक नहीं बढ़ाते हैं।

कहा जाता है कि, भले ही आपको सबसे तेज़ समाधान की आवश्यकता न हो, इस उत्तर के माध्यम से पढ़ना साहसिक और शैक्षिक हो सकता है।

I. सुधार

1. उत्पत्ति (दौड़)

एक अनुस्मारक के रूप में, मूल, सामान्य समाधान जो हम सुधार रहे हैं वह यह है:

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. बाइट्स

यदि अक्षरों को चुनने और यादृच्छिक स्ट्रिंग को इकट्ठा करने के लिए केवल अंग्रेजी वर्णमाला के अपरकेस और लोअरकेस अक्षर होते हैं, तो हम केवल बाइट्स के साथ काम कर सकते हैं क्योंकि UTF-8 एन्कोडिंग में अंग्रेजी वर्णमाला पत्र 1 से 1 बाइट्स के लिए मैप करता है (जो यह है कि गो स्टोर स्ट्रिंग्स)।

इसलिए इसके बजाय:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

हम प्रयोग कर सकते हैं:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

या इससे भी बेहतर:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

अब यह पहले से ही एक बड़ा सुधार है: हम इसे एक होने के लिए प्राप्त कर सकते constहैं ( stringस्थिरांक हैं लेकिन कोई स्लाइस स्थिरांक नहीं हैं )। अतिरिक्त लाभ के रूप में, अभिव्यक्ति len(letters)भी एक होगी const! ( len(s)यदि sस्ट्रिंग स्थिर है तो अभिव्यक्ति स्थिर है।)

और किस कीमत पर? कुछ भी नहीं। strings को अनुक्रमित किया जा सकता है जो अपने बाइट्स को अनुक्रमित करता है, एकदम सही, जो हम चाहते हैं।

हमारा अगला गंतव्य इस तरह दिखता है:

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. अवशेष

पिछले समाधानों को एक यादृच्छिक पत्र प्राप्त करने के लिए एक यादृच्छिक संख्या प्राप्त होती है, जिसे कॉल करके 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 बिट्स तक पहुंच जाता है, तो यह नगण्य है।

4. मास्किंग

पिछले समाधान पर बिल्डिंग, हम अक्षरों की संख्या का प्रतिनिधित्व करने के लिए आवश्यक यादृच्छिक संख्या के सबसे कम बिट्स का उपयोग करके केवल अक्षरों के समान वितरण को बनाए रख सकते हैं। इसलिए उदाहरण के लिए अगर हमारे पास 52 अक्षर हैं, तो इसे प्रस्तुत करने के लिए 6 बिट्स की आवश्यकता होती है 52 = 110100b:। इसलिए हम केवल सबसे कम 6 बिट्स का उपयोग करेंगे जो कि लौटाए गए नंबर से होगाrand.Int63() । और अक्षरों के समान वितरण को बनाए रखने के लिए, हम केवल "संख्या" को स्वीकार करते हैं यदि यह सीमा में आता है 0..len(letterBytes)-1। यदि सबसे कम बिट्स अधिक हैं, तो हम इसे त्याग देते हैं और एक नया यादृच्छिक संख्या क्वेरी करते हैं।

ध्यान दें कि सबसे कम बिट्स की संभावना सामान्य या औसत len(letterBytes)से कम से अधिक होने की संभावना है , जिसका अर्थ है कि भले ही यह मामला होगा, इस "दुर्लभ" मामले को दोहराते हुए एक अच्छा नहीं मिलने की संभावना कम हो जाती है नंबर। पुनरावृत्ति के बाद , मौका है कि हम एक अच्छा सूचकांक नहीं है की तुलना में बहुत कम है , और यह सिर्फ एक ऊपरी आकलन है। 52 अक्षरों के मामले में मौका है कि 6 सबसे कम बिट अच्छे नहीं हैं ; उदाहरण के लिए जिसका अर्थ है कि 10 पुनरावृत्ति के बाद एक अच्छी संख्या नहीं होने की संभावना है ।0.50.25npow(0.5, n)(64-52)/64 = 0.191e-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)
}

5. मास्किंग में सुधार

पिछला समाधान केवल 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)
}

6. स्रोत

मास्किंग सुधार , बहुत अच्छी है बहुत ज्यादा नहीं हम इस पर सुधार कर सकते हैं। हम कर सकते हैं, लेकिन जटिलता के लायक नहीं।

अब हम कुछ और सुधारने की कोशिश करते हैं। यादृच्छिक संख्याओं का स्रोत।

एक 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द्वारा लौटाए जाने की संभावना अधिक तेज़ है)।

7. उपयोग करना 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()विधि को बुलाया , यह सुनिश्चित करते हुए कि यह एक बड़े-पर्याप्त आंतरिक स्लाइस (वास्तविक अक्षरों को जोड़ने से बचने के लिए) को आवंटित करता है।

8. 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))
}

(9. उपयोग करते हुए 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.Sourcerand.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(), का उपयोग करता छठे स्मृति और कुछ आवंटन के रूप में आधा । मिशन पूरा हुआ।


8
@ रोबी वीप, क्योंकि एक साझा rand.Sourceका उपयोग किया जाता है। एक बेहतर वर्कअराउंड फ़ंक्शन को पास rand.Sourceकरने के लिए होगा RandStringBytesMaskImprSrc(), और इस तरह कोई लॉकिंग की आवश्यकता नहीं है और इसलिए प्रदर्शन / दक्षता प्रभावित नहीं होती है। प्रत्येक गोरोइन का अपना हो सकता है Source
13

113
@icza, जो कि SO पर एक लंबे समय के लिए देखे गए सबसे अच्छे उत्तरों में से एक है!
एस्ट्रोपेनिक

1
@ माइक एटलस: deferजब यह स्पष्ट हो कि आपको इसकी आवश्यकता नहीं है तो इसका उपयोग करने से बचना चाहिए । देखें grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx

1
टिप के लिए @ZanLynx thx; हालाँकि deferकिसी लॉक को कॉल करने से पहले या बाद में म्यूटेक्स को अनलॉक करने के लिए आईएमओ ज्यादातर एक बहुत अच्छा विचार है; आप दोनों को अनलॉक न करने की गारंटी देते हैं, बल्कि एक गैर-घातक आतंक मध्य-समारोह में भी अनलॉक करने की गारंटी देते हैं।
माइक एटलस

1
@RobbieV ऐसा लगता है कि यह कोड थ्रेड / गोरोइन सुरक्षित है क्योंकि अंतर्निहित साझा स्रोत पहले से ही एक लॉकडसोर्स है जो म्यूटेक्स ( golang.org/src/math/rand/rand.go.259 ) को लागू करता है ।
२०:५० पर आदित्यजोन

130

आप इसके लिए सिर्फ कोड लिख सकते हैं। यह कोड थोड़ा सरल हो सकता है यदि आप 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
Rand.Seed () के बारे में मत भूलना, अन्यथा आपको हर बार पहली बार लॉन्च होने वाला एक ही स्ट्रिंग ... rand.Seed (time.Now ()। UTC ()। UnixNano ())
इवान लिन

2
इवान का जोड़ सही है, हालांकि अन्य समान विकल्प भी हैं: rand.Seed(time.Now().Unix())याrand.Seed(time.Now().UnixNano())
21

7
हार्ड-टू-अनुमान रहस्य के लिए - एक पासवर्ड, एक क्रिप्टो कुंजी, आदि ।-- कभी भी उपयोग न करें math/rand; उपयोग crypto/rand(जैसे @ Not_A_Golfer का विकल्प 1)।
twotwotwo

1
@ इवानलिन क्या यह अनुमान नहीं होगा? अगर मुझे जनरेटर को बीज देना है, तो हमलावर समय का अनुमान लगा सकता है कि मैं इसके साथ बीजारोपण कर रहा हूं और उसी आउटपुट का अनुमान लगा सकता हूं जो मैं पैदा कर रहा हूं।
मतेज

4
ध्यान दें कि यदि आप उपरोक्त कार्यक्रम को बीज के साथ खेल के मैदान पर आजमा रहे हैं, तो यह हर समय एक ही परिणाम देगा। मैं इसे खेल के मैदान पर आजमा रहा था और कुछ समय बाद यह एहसास हुआ। यह मेरे लिए अन्यथा ठीक काम किया। आशा है कि यह किसी को समय बचाता है :)
गौरव सिन्हा

18

पैकेज यूनीरी का उपयोग करें , जो क्रिप्टोग्राफिक रूप से सुरक्षित वर्दी (निष्पक्ष) तार उत्पन्न करता है।

अस्वीकरण: मैं पैकेज का लेखक हूं


1
एक तरफ: लेखक, dchest, एक उत्कृष्ट डेवलपर है और उसने इस तरह के कई छोटे, उपयोगी पैकेज बनाए हैं।
रोशाम्बो

16

दो संभावित विकल्प (निश्चित रूप से अधिक हो सकते हैं):

  1. आप उस crypto/randपैकेज का उपयोग कर सकते हैं जो रैंडम बाइट एरेज़ (/ / dev / urandom) को पढ़ने का समर्थन करता है और क्रिप्टोग्राफ़िक रैंडम जेनरेशन की ओर तैयार है। http://golang.org/pkg/crypto/rand/#example_Read देखें । यह हालांकि सामान्य छद्म यादृच्छिक संख्या पीढ़ी की तुलना में धीमा हो सकता है।

  2. एक यादृच्छिक संख्या लें और इसे md5 या कुछ इस तरह से उपयोग करें।


4

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


2

यदि आप क्रिप्टोग्राफिक रूप से सुरक्षित रैंडम नंबर चाहते हैं , और सटीक चारसेट लचीला है (मान लीजिए, बेस 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


यह ध्यान देने योग्य है कि गो प्लेग्राउंड हमेशा एक ही रैंडम नंबर देता है, इसलिए आप उस कोड के विभिन्न निष्पादन में अलग-अलग रैंडम स्ट्रिंग्स नहीं देखेंगे
TPPZ


1

यहाँ मेरा तरीका है) अपनी इच्छानुसार गणित रैंड या क्रिप्टो रैंड का उपयोग करें।

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

यदि आप अनुमत वर्णों के अपने पूल में कुछ वर्ण जोड़ने के लिए तैयार हैं, तो आप कुछ भी के साथ कोड काम कर सकते हैं जो एक 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जरूरी है?
सुंग चो

2
क्योंकि len(encodeURL) == 64। अगर random % 64ऐसा नहीं किया गया था, तो randomPosरनिंग के समय घबराहट होने की संभावना है।
0xcaff

-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))
}

बेंचमार्करंडस्ट्र 16-8 20000000 68.1 ns / op 16 B / op 1 ऑलोकस / ऑप

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.