Bir Dizedeki Karakterlerin sayısı nasıl alınır?


145

Go'da bir dizenin karakter sayısını nasıl alabilirim?

Örneğin, bir dize varsa "hello"yöntem dönmelidir 5. Ben karakter sayısını değillen(str) bayt sayısını döndürdüğünü gördüm, bu yüzden £ UTF-8'de iki bayt ile kodlandığı için 1 yerine 2 döndürür.len("£")


2
Geri dönüyor 5 . Dosya kodlaması UTF-8 olduğunda belki olmayabilir.
Moshe Revah

7
Evet, bu durum için geçerlidir, ancak Arapça gibi 1 bayta çevrilmeyen diğer UTF-8 karakterleri için genel yapmak istiyorum.
Ammar

Yanıtlar:


177

RuneCountInStringUtf8 paketinden deneyebilirsiniz .

p'de runes sayısını döndürür

Bu senaryoda gösterildiği gibi : "Dünya" nın uzunluğu 6 olabilir (Çince yazıldığında: "世界"), ancak rune sayısı 2'dir:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen ekler yorumlarda :

Aslında len()sadece döküm yazarak runes üzerinden yapabilirsiniz .
len([]rune("世界"))yazdıracaktır 2. Go 1.3'teki sıçramalarda.


Ve birlikte CL 108985 (Mayıs 2018, Go 1.11 için) len([]rune(string))ile şimdi optimize edildi. ( 24923 sayısını giderir )

Derleyici len([]rune(string))kalıbı otomatik olarak algılar ve r: = aralık s aramasıyla değiştirir.

Bir dizedeki runeleri saymak için yeni bir çalışma zamanı işlevi ekler. Deseni algılamak için derleyiciyi değiştirir ve len([]rune(string)) yeni rune sayma çalışma zamanı işleviyle değiştirir.

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)

Stefan Steiger blog yazısı "işaret git Metin normalleşme "

Karakter nedir?

Dizeler blog gönderisinde belirtildiği gibi , karakterler birden fazla runeye yayılabilir .
Örneğin, bir ' e' ve '◌́◌́' (akut "\ u0301"), NFD'de 'é' (" e\u0301") oluşturmak için birleşebilir . Bu iki run birlikte bir karakterdir .

Bir karakterin tanımı, uygulamaya bağlı olarak değişebilir. Normalleştirme
için bunu şöyle tanımlayacağız:

  • bir marşla başlayan bir dizi runik,
  • diğer runeleri değiştirmeyen veya geriye doğru birleştirmeyen bir rune,
  • akabinde, boş olmayan başlatıcılar, yani yapan runeler dizisi (tipik olarak aksanlar).

Normalleştirme algoritması bir seferde bir karakteri işler.

Bu paketi ve Itertürünü kullanarak , gerçek "karakter" sayısı:

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

Burada, bu Unicode Normalizasyon formunu kullanır NFKD "Uyumluluk Ayrıştırma"


Oliver 'ın cevabı işaret UNICODE METİN BÖLÜMLEMESİNE kullanıcı tarafından algılanan karakterler, kelimeler ve cümleler: güvenilir belli önemli metin elemanları arasında varsayılan sınırları belirleyen tek yolu olarak.

Bunun için Unicode Metin Segmentasyonu yapan rivo / uniseg gibi harici bir kütüphaneye ihtiyacınız var .

Bu, aslında birden çok kod noktasının kullanıcı tarafından algılanan bir karakterle birleştirilebileceği " grafik kümesi " ni sayar .

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

Üç rün (Unicode kod noktaları) olmasına rağmen iki grafik.

" GO'da dizeleri tersine çevirmek için nasıl değiştirilir? "

🦰🏾‍🦰 tek başına bir grafiktir, ancak unicode'dan kod noktaları dönüştürücüsüne , 4 runik:


4
Bu dize değiştirme işlevinde
çalışırken bunu

5
Bu size sadece runik sayısını gösterir, glif sayısını değil. Birçok glif birden fazla rundan yapılır.
Stephen Weinberg

5
Aslında, sadece döküm yazarak len () runes üzerinde yapabilirsiniz ... len ([] rune ("世界")) 2 yazdıracak.
Phrozen

3
@VonC: Aslında, bir karakter (Glif için konuşma dili terimi) - bazen - birkaç runik yayılabilir, bu nedenle bu cevap, kesin teknik terimi WRONG kullanmaktır. İhtiyacınız olan şey rune sayısı değil Grapheme / GraphemeCluster sayısıdır. Örneğin, bir 'e' ve '◌́' (akut "\ u0301") 'é' (NFD'de "e \ u0301") oluşturmak için birleşebilir. Fakat bir insan (doğru bir şekilde) saygı duyar ve eacute eder; Bir karakter olarak .. Görünüşe göre Telugu'da bir fark yaratıyor. Ama muhtemelen Fransızca, kullandığınız klavye / yerel ayarına bağlı olarak. blog.golang.org/normalization
Stefan Steiger

1
@JustinJohnson Kabul etti. Cevabı daha önce iptal ettiğim Oliver'ın daha iyi referansı için düzenledim.
VonC

43

Dizeyi [] runeye şu şekilde dönüştürerek herhangi bir paket olmadan runik sayısını almanın bir yolu vardır 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)))

}

bayt sayısı 30 16

run sayısı 16 16


5

Bir "karakterin" ne olduğunu tanımınıza çok bağlıdır. Eğer "rune bir karaktere eşittir" göreviniz için uygunsa (genellikle değil), o zaman VonC'nin cevabı sizin için mükemmeldir. Aksi takdirde, Unicode dizesindeki runlerin sayısının ilginç bir değer olduğu birkaç durum olduğu belirtilmelidir. Ve bu durumlarda bile, UTF-8 kod çözme çabasını ikiye katlamaktan kaçınmak için runeler işlenirken ipi "çaprazlarken" sayımı çıkarmak daha iyidir.


Eğer ne zaman değil bir karakter olarak bir rune görüyor musunuz? Go spec bir runeyi Unicode kod noktası olarak tanımlar: golang.org/ref/spec#Rune_literals .
Thomas Kappler

Ayrıca, kod çözme çabasını ikiye katlamaktan kaçınmak için, sadece [] rune (str) yapıyorum, bunun üzerinde çalışıyorum, sonra işimi bitirdiğimde dizeye geri dönüyorum. Bu bir dize geçerken kod noktalarını takip etmekten daha kolay olduğunu düşünüyorum.
Thomas Kappler

4
@ThomasKappler: Ne zaman? Rune, genellikle olmayan bir karakter olmadığında. Sadece bazı runeler karakterlere eşittir, hepsi değil. "Rune == character" varsayıldığında, yalnızca Unicode karakterlerin bir alt kümesi için geçerlidir. Örnek: en.wikipedia.org/wiki/…
zzzz

@ThomasKappler: Eğer bu açıdan bakarsak, o zaman örneğin Java en String'in .length()metodu ya karakter sayısını döndürmez. Ne Kakao en does NSString'ın -lengthyöntemi. Bunlar sadece UTF-16 varlıklarının sayısını döndürür. Ancak gerçek kod noktası sayısı nadiren kullanılır, çünkü saymak doğrusal zaman alır.
newacct

5

Grafeme kümelerini dikkate almanız gerekiyorsa, regexp veya unicode modülünü kullanın. Doğrulama için kod noktalarının (runiklerin) veya baytların sayılması da gereklidir, çünkü grafik kümesi kümesinin uzunluğu sınırsızdır. Aşırı uzun dizileri ortadan kaldırmak istiyorsanız, dizilerin akış açısından güvenli metin biçimine uygun olup olmadığını kontrol edin .

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

Bunun için teşekkürler. Kodunuzu denedim ve bu gibi birkaç emoji grafiği için çalışmıyor: 🖖🏿🇸🇴. Bunları nasıl doğru bir şekilde sayacağınız hakkında herhangi bir düşünceniz var mı?
Bjorn Roche

Derlenen normal ifade varişlevlerin dışında olduğu gibi çıkarılmalıdır .
dolmen

5

Bir dize uzunluğu almanın birkaç yolu vardır:

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

Şimdiye kadar verilen cevapların hiçbirinin, özellikle emojilerle (aynı zamanda Tay dili, Korece veya Arapça gibi bazı dillerle) uğraşırken, beklediğiniz kadar karakter vermediğini belirtmeliyim. VonC'nin önerileri aşağıdakilerin çıktısını verecektir:

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

Çünkü bu yöntemler yalnızca Unicode kod noktalarını sayar. Birden fazla kod noktasından oluşabilen birçok karakter vardır.

Normalleştirme paketini kullanmak için aynı :

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

Normalleştirme karakter saymakla aynı değildir ve birçok karakter tek kodlu bir eşdeğeri normalleştirilemez.

masakielastic'in yanıtı yaklaşıyor, ancak sadece değiştiricileri işliyor (gökkuşağı bayrağı kendi kod noktası olarak sayılmayan bir değiştirici içeriyor):

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

Unicode dizelerini (kullanıcı tarafından algılanan) karakterlere, yani grafik kümelerine bölmenin doğru yolu Unicode Standart Ek # 29'da tanımlanmıştır . Kurallar Bölüm 3.1.1'de bulunabilir . Github.com/rivo/uniseg paket uygular bu kurallar bir dizesindeki karakter sayısını doğru belirlemek, böylece:

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

0

Normalleştirmeyi biraz daha hızlı yapmaya çalıştım:

    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
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.