एक स्ट्रिंग में वर्णों की संख्या कैसे प्राप्त करें?


145

मुझे गो में स्ट्रिंग के पात्रों की संख्या कैसे मिल सकती है?

उदाहरण के लिए, यदि मेरे पास एक स्ट्रिंग है "hello"तो विधि वापस आनी चाहिए 5। मैंने देखा कि len(str)बाइट्स की संख्या लौटाता है और वर्णों की संख्या len("£")1 के बजाय 2 नहीं लौटाता है क्योंकि £ यूटीएफ -8 में दो बाइट्स के साथ एन्कोडेड है।


2
यह 5 वापसी करता है । शायद यह नहीं है जब फ़ाइल एन्कोडिंग UTF-8 है।
मोशे रेवह

7
हां यह इस मामले के लिए करता है, लेकिन मैं इसे अन्य UTF-8 वर्णों जैसे अरबी के लिए सामान्य बनाना चाहता हूं, जो 1 बाइट में अनुवाद नहीं करता है।
अम्मर

जवाबों:


177

आप RuneCountInStringutf8 पैकेज से कोशिश कर सकते हैं ।

पी में रन की संख्या देता है

जैसा कि इस लिपि में दर्शाया गया है : "विश्व" की लंबाई 6 हो सकती है (जब चीनी में लिखी गई हो: "世界"), लेकिन इसकी रन संख्या 2 है:

package main

import "fmt"
import "unicode/utf8"

func main() {
    fmt.Println("Hello, 世界", len("世界"), utf8.RuneCountInString("世界"))
}

Phrozen टिप्पणियों में जोड़ता है :

वास्तव में आप len()केवल टाइप कास्टिंग के द्वारा रन से अधिक कर सकते हैं ।
len([]rune("世界"))छप जाएगा 2। 1.3 गो में लीट पर।


और CL 108985 (मई 2018, गो 1.11 के लिए) के साथ, len([]rune(string))अब अनुकूलित है। (फिक्स २४ ९ २३ जारी )

कंपाइलर len([]rune(string))पैटर्न को स्वचालित रूप से पता लगाता है , और इसे r: = रेंज s कॉल के साथ बदल देता है।

एक स्ट्रिंग में रन को गिनने के लिए एक नया रनटाइम फ़ंक्शन जोड़ता है। कंपाइलर को पैटर्न का पता लगाने के लिए संशोधित करता है len([]rune(string)) और इसे नए रनिंग काउंटिंग रनटाइम फ़ंक्शन के साथ बदलता है।

RuneCount/lenruneslice/ASCII                  27.8ns ± 2%  14.5ns ± 3%  -47.70%  (p=0.000 n=10+10)
RuneCount/lenruneslice/Japanese                126ns ± 2%    60ns ± 2%  -52.03%  (p=0.000 n=10+10)
RuneCount/lenruneslice/MixedLength             104ns ± 2%    50ns ± 1%  -51.71%  (p=0.000 n=10+9)

स्टीफन स्टीगर ब्लॉग पोस्ट "की ओर इशारा जाओ में पाठ सामान्य "

एक चरित्र क्या है?

जैसा कि स्ट्रिंग्स ब्लॉग पोस्ट में उल्लेख किया गया था , वर्ण कई रनों को फैला सकते हैं
उदाहरण के लिए, एक ' e' और '◌́◌́' (तीव्र "\ u0301") e\u0301NFD में 'é' (" ") बनाने के लिए संयोजन कर सकता है । एक साथ ये दो रन एक चरित्र हैं

एक चरित्र की परिभाषा आवेदन के आधार पर भिन्न हो सकती है।
के लिए सामान्य हम इसे के रूप में परिभाषित करेगा:

  • रनर का एक क्रम, जो स्टार्टर से शुरू होता है,
  • एक रनर जो किसी अन्य रनर के साथ पीछे की ओर संशोधित या संयोजित नहीं होता है,
  • संभवतः गैर-शुरुआत के खाली अनुक्रम के बाद, अर्थात, रन (जो आमतौर पर उच्चारण करता है)।

सामान्यकरण एल्गोरिथ्म एक समय में एक चरित्र को संसाधित करता है।

उस पैकेज और उसके Iterप्रकार का उपयोग करते हुए , "वर्ण" की वास्तविक संख्या होगी:

package main

import "fmt"
import "golang.org/x/text/unicode/norm"

func main() {
    var ia norm.Iter
    ia.InitString(norm.NFKD, "école")
    nc := 0
    for !ia.Done() {
        nc = nc + 1
        ia.Next()
    }
    fmt.Printf("Number of chars: %d\n", nc)
}

यहाँ, यह यूनिकोड सामान्यीकरण प्रपत्र NFKD "संगतता अपघटन" का उपयोग करता है


ओलिवर का उत्तर अंक कुछ महत्वपूर्ण पाठ तत्वों के बीच डिफ़ॉल्ट सीमाओं को मज़बूती से निर्धारित करने के एकमात्र तरीके के रूप में UNICODE TEXT SEGMENTATION को इंगित करता है: उपयोगकर्ता-कथित वर्ण, शब्द और वाक्य।

उसके लिए, आपको एक बाहरी लाइब्रेरी चाहिए जैसे कि rivo / यूनिसेग , जो यूनिकोड टेक्स्ट सेगमेंटेशन करता है ।

यह वास्तव में " ग्रैफेम क्लस्टर " की गणना करेगा , जहां कई कोड बिंदुओं को एक उपयोगकर्ता-माना चरित्र में जोड़ा जा सकता है।

package uniseg

import (
    "fmt"

    "github.com/rivo/uniseg"
)

func main() {
    gr := uniseg.NewGraphemes("👍🏼!")
    for gr.Next() {
        fmt.Printf("%x ", gr.Runes())
    }
    // Output: [1f44d 1f3fc] [21]
}

दो अंगूर, भले ही तीन रन (यूनिकोड कोड पॉइंट) हों।

आप अन्य उदाहरणों में देख सकते हैं कि " उन्हें रिवर्स करने के लिए GO में स्ट्रिंग्स में हेरफेर कैसे करें? "

,👩🏾hem अकेले एक अंगूर है, लेकिन, यूनिकोड से कोड पॉइंट कनवर्टर तक , 4 रन:


4
आप इस स्ट्रिंग प्रत्यावर्तन समारोह में कार्रवाई में इसे देख सकते हैं stackoverflow.com/a/1758098/6309
VonC

5
यह आपको केवल रनों की संख्या बताता है, ग्लिफ़ की संख्या नहीं। कई ग्लिफ़ कई रन से बने होते हैं।
स्टीफन वेनबर्ग

5
वास्तव में आप सिर्फ टाइप कास्ट द्वारा रन पर लेन () कर सकते हैं ... लेन ([] रनवे ("print") प्रिंट करेंगे 2. गो 1.3 में लेट्स पर, कब तक ऐसा किया गया है।
Phrozen

3
@VonC: वास्तव में, एक चरित्र (ग्लिफ़ के लिए बोलचाल की भाषा का शब्द) - कभी-कभी - कई रनों को फैला सकता है, इसलिए यह उत्तर सटीक तकनीकी शब्द, गलत का उपयोग करने के लिए है। क्या आप की जरूरत है Grapheme / GraphemeCluster गिनती है, न कि रूनी गिनती। उदाहरण के लिए, एक 'e' और '◌́' (एक्यूट "\ u0301") मिलकर 'é' ("e \ u0301" को NFD में बना सकते हैं)। लेकिन एक मानव होगा (सही ढंग से) संबंध और उत्सुकता; एक चरित्र के रूप में .. जाहिर तौर पर यह तेलुगु में एक अंतर है। लेकिन शायद आपके द्वारा उपयोग किए जाने वाले कीबोर्ड / लोकेल के आधार पर भी फ्रेंच। blog.golang.org/normalization
स्टीफन स्टीगर

1
@JustinJohnson सहमत। मैंने बेहतर संदर्भ ओलिवर के उत्तर को संपादित किया है, कि मैंने पहले उत्थान किया था।
VonC

43

स्ट्रिंग को [] के रूप में परिवर्तित करके किसी भी पैकेज के बिना रन की गिनती प्राप्त करने का एक तरीका है len([]rune(YOUR_STRING)) :

package main

import "fmt"

func main() {
    russian := "Спутник и погром"
    english := "Sputnik & pogrom"

    fmt.Println("count of bytes:",
        len(russian),
        len(english))

    fmt.Println("count of runes:",
        len([]rune(russian)),
        len([]rune(english)))

}

बाइट्स की गिनती 30 16

रन की गिनती 16 16


5

एक "चरित्र" क्या है की आपकी परिभाषा पर बहुत कुछ निर्भर करता है। यदि आपके कार्य के लिए "रूना एक वर्ण के बराबर है" ठीक है (आमतौर पर यह नहीं है) तो VonC द्वारा उत्तर आपके लिए एकदम सही है। अन्यथा, यह संभवतः ध्यान दिया जाना चाहिए, कि कुछ परिस्थितियां हैं जहां यूनिकोड स्ट्रिंग में रन की संख्या एक दिलचस्प मूल्य है। और यहां तक ​​कि उन स्थितियों में भी यह बेहतर है, यदि संभव हो तो, स्ट्रिंग को "ट्रेसिंग" करते समय "ट्रेसिंग" करें क्योंकि रन को यूटीएफ -8 डिकोड प्रयास को दोगुना करने से बचने के लिए संसाधित किया जाता है।


जब आप एक पात्र को एक चरित्र के रूप में नहीं देखेंगे? गो स्पेक एक रन को एक यूनिकोड कोडपॉइंट के रूप में परिभाषित करता है: golang.org/ref/spec#Rune_literals
थॉमस कप्पलर

इसके अलावा, डिकोड प्रयास को दोगुना करने से बचने के लिए, मैं सिर्फ एक [] रूनी (str) काम करता हूं, उस पर काम करता हूं, फिर जब मैं काम करता हूं तो वापस स्ट्रिंग में परिवर्तित करता हूं। मुझे लगता है कि एक स्ट्रिंग को पार करते समय कोड बिंदुओं पर नज़र रखना आसान है।
थॉमस काप्पलर

4
@ThomasKappler: कब? ठीक है, जब रूण एक चरित्र नहीं है, जो कि यह आम तौर पर नहीं है। केवल कुछ रन पात्रों के बराबर हैं, उनमें से सभी नहीं। "Rune == वर्ण" मानकर केवल यूनिकोड वर्णों के सबसेट के लिए मान्य है। उदाहरण: en.wikipedia.org/wiki/…
zzzz

@ThomasKappler: लेकिन यदि आप इसे पर कि जिस तरह से लग रहे हैं, तो उदाहरण के लिए जावा के Stringके .length()विधि वर्णों की संख्या वापस नहीं करता है या तो। न तो करता है कोको के NSStringकी -lengthविधि। वे केवल UTF-16 संस्थाओं की संख्या लौटाते हैं। लेकिन कोडपॉइंट्स की सही संख्या का उपयोग शायद ही कभी किया जाता है, क्योंकि इसे गिनने में रैखिक समय लगता है।
newacct

5

यदि आपको अंगूर के गुच्छों को ध्यान में रखना है, तो रेगेक्सपी या यूनिकोड मॉड्यूल का उपयोग करें। ग्रिपहेम क्लस्टर की लंबाई असीमित होने के बाद से वैलिडेटन के लिए कोड पॉइंट्स (रन) या बाइट्स की संख्या की गणना भी आवश्यक है। यदि आप बहुत लंबे अनुक्रमों को समाप्त करना चाहते हैं, तो जांचें कि क्या अनुक्रम धारा-सुरक्षित पाठ प्रारूप के अनुरूप हैं ।

package main

import (
    "regexp"
    "unicode"
    "strings"
)

func main() {

    str := "\u0308" + "a\u0308" + "o\u0308" + "u\u0308"
    str2 := "a" + strings.Repeat("\u0308", 1000)

    println(4 == GraphemeCountInString(str))
    println(4 == GraphemeCountInString2(str))

    println(1 == GraphemeCountInString(str2))
    println(1 == GraphemeCountInString2(str2))

    println(true == IsStreamSafeString(str))
    println(false == IsStreamSafeString(str2))
}


func GraphemeCountInString(str string) int {
    re := regexp.MustCompile("\\PM\\pM*|.")
    return len(re.FindAllString(str, -1))
}

func GraphemeCountInString2(str string) int {

    length := 0
    checked := false
    index := 0

    for _, c := range str {

        if !unicode.Is(unicode.M, c) {
            length++

            if checked == false {
                checked = true
            }

        } else if checked == false {
            length++
        }

        index++
    }

    return length
}

func IsStreamSafeString(str string) bool {
    re := regexp.MustCompile("\\PM\\pM{30,}") 
    return !re.MatchString(str) 
}

इसके लिए धन्यवाद। मैंने आपके कोड की कोशिश की और यह इन जैसे कुछ इमोजी अंगूर के लिए काम नहीं करता है: it। कैसे सही गिनती करने के बारे में कोई विचार?
ब्योर्न रोश

संकलित regexp को varफ़ंक्शन के बाहर के रूप में निकाला जाना चाहिए ।
dolmen

5

एक स्ट्रिंग लंबाई पाने के कई तरीके हैं:

package main

import (
    "bytes"
    "fmt"
    "strings"
    "unicode/utf8"
)

func main() {
    b := "这是个测试"
    len1 := len([]rune(b))
    len2 := bytes.Count([]byte(b), nil) -1
    len3 := strings.Count(b, "") - 1
    len4 := utf8.RuneCountInString(b)
    fmt.Println(len1)
    fmt.Println(len2)
    fmt.Println(len3)
    fmt.Println(len4)

}

3

मुझे यह बताना चाहिए कि अब तक प्रदान किए गए उत्तरों में से कोई भी आपको वर्णों की संख्या नहीं देगा जैसा कि आप उम्मीद करेंगे, खासकर जब आप इमोजीस (लेकिन थाई, कोरियाई या अरबी जैसी कुछ भाषाएं) के साथ काम कर रहे हों। VonC के सुझाव निम्न आउटपुट देंगे:

fmt.Println(utf8.RuneCountInString("🏳️‍🌈🇩🇪")) // Outputs "6".
fmt.Println(len([]rune("🏳️‍🌈🇩🇪"))) // Outputs "6".

ऐसा इसलिए है क्योंकि ये विधियां केवल यूनिकोड कोड बिंदुओं की गणना करती हैं। कई वर्ण हैं जो कई कोड बिंदुओं से बना हो सकते हैं।

सामान्यीकरण पैकेज का उपयोग करने के लिए समान :

var ia norm.Iter
ia.InitString(norm.NFKD, "🏳️‍🌈🇩🇪")
nc := 0
for !ia.Done() {
    nc = nc + 1
    ia.Next()
}
fmt.Println(nc) // Outputs "6".

सामान्यीकरण वास्तव में वर्णों की गिनती के समान नहीं है और कई पात्रों को एक-कोड-पॉइंट समकक्ष में सामान्यीकृत नहीं किया जा सकता है।

मासाकिलेस्टिक का जवाब करीब आता है, लेकिन केवल संशोधक को संभालता है (इंद्रधनुष के झंडे में एक संशोधक होता है जिसे इस प्रकार अपने स्वयं के कोड बिंदु के रूप में नहीं गिना जाता है):

fmt.Println(GraphemeCountInString("🏳️‍🌈🇩🇪"))  // Outputs "5".
fmt.Println(GraphemeCountInString2("🏳️‍🌈🇩🇪")) // Outputs "5".

यूनिकोड के तारों को (उपयोगकर्ता-कथित) वर्णों में विभाजित करने का सही तरीका, यानी कि अंगूर समूह, यूनिकोड मानक अनुलग्नक # 29 में परिभाषित किया गया है । नियम धारा 3.1.1 में पाए जा सकते हैं । Github.com/rivo/uniseg पैकेज लागू इन नियमों आप एक स्ट्रिंग में वर्णों की सही संख्या का निर्धारण कर सकते हैं ताकि:

fmt.Println(uniseg.GraphemeClusterCount("🏳️‍🌈🇩🇪")) // Outputs "2".

0

मैंने सामान्यीकरण को थोड़ा तेज़ करने की कोशिश की:

    en, _ = glyphSmart(data)

    func glyphSmart(text string) (int, int) {
        gc := 0
        dummy := 0
        for ind, _ := range text {
            gc++
            dummy = ind
        }
        dummy = 0
        return gc, dummy
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.