Làm thế nào để có được số lượng ký tự trong một chuỗi?


145

Làm cách nào tôi có thể nhận được số lượng ký tự của một chuỗi trong Go?

Ví dụ, nếu tôi có một chuỗi "hello", phương thức sẽ trả về 5. Tôi thấy rằng len(str)trả về số byte chứ không phải số ký tự nên len("£")trả về 2 thay vì 1 vì £ được mã hóa bằng hai byte trong UTF-8.


2
Nó trả về 5 . Có lẽ nó không có khi mã hóa tập tin là UTF-8.
Moshe Revah

7
Có, trong trường hợp này, nhưng tôi muốn đặt nó chung cho các ký tự UTF-8 khác như tiếng Ả Rập, không dịch sang 1 byte.
Ammar

Câu trả lời:


177

Bạn có thể thử RuneCountInStringtừ gói utf8.

trả về số lượng rune trong p

rằng, như được minh họa trong kịch bản này : độ dài của "Thế giới" có thể là 6 (khi được viết bằng tiếng Trung: ""), nhưng số lượng rune của nó là 2:

package main

import "fmt"
import "unicode/utf8"

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

Ph Frozen cho biết thêm trong các ý kiến :

Trên thực tế bạn có thể làm len()hơn runes chỉ bằng cách gõ.
len([]rune("世界"))sẽ in 2. Tại các nhánh trong Go 1.3.


Và với CL 108985 (tháng 5 năm 2018, cho Go 1.11), len([]rune(string))giờ đã được tối ưu hóa. ( Khắc phục sự cố 24923 )

Trình biên dịch len([]rune(string))tự động phát hiện mẫu và thay thế nó bằng lệnh gọi r: = Range s.

Thêm một chức năng thời gian chạy mới để đếm rune trong một chuỗi. Sửa đổi trình biên dịch để phát hiện mẫu len([]rune(string)) và thay thế nó bằng hàm rune đếm thời gian chạy mới.

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 chỉ vào bài đăng trên blog "Bình thường hóa văn bản trong Go "

Một nhân vật là gì?

Như đã đề cập trong bài viết trên blog chuỗi , các ký tự có thể trải rộng trên nhiều rune .
Ví dụ: một ' e' và '◌́◌́' (cấp tính "\ u0301") có thể kết hợp để tạo thành 'é' (" e\u0301" trong NFD). Hai rune cùng nhau là một nhân vật .

Định nghĩa của một nhân vật có thể thay đổi tùy thuộc vào ứng dụng.
Để chuẩn hóa, chúng tôi sẽ định nghĩa nó là:

  • một chuỗi các rune bắt đầu với một khởi đầu,
  • một rune không sửa đổi hoặc kết hợp ngược với bất kỳ rune khác,
  • theo sau là chuỗi không có khả năng khởi động trống, nghĩa là rune làm (thường là dấu).

Thuật toán chuẩn hóa xử lý một ký tự tại một thời điểm.

Sử dụng gói đó và Iterloại của nó , số "ký tự" thực tế sẽ là:

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

Ở đây, điều này sử dụng biểu mẫu Chuẩn hóa Unicode "Phân tích tương thích"


Câu trả lời của Oliver chỉ ra GIỚI THIỆU VĂN BẢN UNICODE là cách duy nhất để xác định đáng tin cậy ranh giới mặc định giữa các yếu tố văn bản quan trọng nhất định: ký tự, từ và câu nhận thức của người dùng.

Để làm được điều đó, bạn cần một thư viện bên ngoài như rivo / uniseg , phân đoạn văn bản Unicode .

Điều đó thực sự sẽ tính " cụm grapheme ", trong đó nhiều điểm mã có thể được kết hợp thành một ký tự do người dùng cảm nhận.

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

Hai biểu đồ, mặc dù có ba rune (điểm mã Unicode).

Bạn có thể xem các ví dụ khác trong " Cách thao tác chuỗi trong GO để đảo ngược chúng? "

Một mình là một grapheme, nhưng, từ unicode sang bộ chuyển đổi điểm mã , 4 rune:


4
Bạn có thể thấy nó hoạt động trong chức năng đảo ngược chuỗi này tại stackoverflow.com/a/1758098/6309
VonC

5
Điều này chỉ cho bạn biết số lượng rune, không phải số lượng glyphs. Nhiều glyphs được làm từ nhiều rune.
Stephen Weinberg

5
Trên thực tế, bạn có thể thực hiện len () trên rune chỉ bằng cách gõ ... len ([] rune ("")) sẽ in 2. Tại các nhánh trong Go 1.3, dunno đã bao lâu rồi.
Ph Frozen

3
@VonC: Trên thực tế, một ký tự (thuật ngữ ngôn ngữ thông tục cho Glyph) có thể - thỉnh thoảng - kéo dài một số rune, vì vậy câu trả lời này là, để sử dụng thuật ngữ kỹ thuật chính xác, SAU. Những gì bạn cần là số lượng Grapheme / GraphemeCluster, không phải số lượng rune. Ví dụ: một 'e' và '◌́' (cấp tính "\ u0301") có thể kết hợp để tạo thành 'é' ("e \ u0301" trong NFD). Nhưng một người sẽ (chính xác) quan tâm & eacute; như MỘT ký tự .. Rõ ràng nó tạo ra sự khác biệt trong tiếng Telugu. Nhưng có lẽ cũng là tiếng Pháp, tùy thuộc vào bàn phím / ngôn ngữ bạn sử dụng. blog.golang.org/n normalization
Stefan Steiger

1
@JustinJohnson Đồng ý. Tôi đã chỉnh sửa câu trả lời để tham khảo tốt hơn Oliver, mà trước đây tôi đã nêu lên.
VonC

42

Có một cách để lấy số lượng rune mà không cần bất kỳ gói nào bằng cách chuyển đổi chuỗi thành [] rune như 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)))

}

số byte 30 16

số lượng rune 16 16


5

Phụ thuộc rất nhiều vào định nghĩa của bạn về "nhân vật" là gì. Nếu "rune bằng một ký tự" là ổn đối với nhiệm vụ của bạn (nói chung là không) thì câu trả lời của VonC là hoàn hảo cho bạn. Mặt khác, có lẽ cần lưu ý rằng, có một vài tình huống trong đó số lượng rune trong chuỗi Unicode là một giá trị thú vị. Và ngay cả trong những tình huống đó, tốt hơn là, nếu có thể, hãy suy ra số đếm trong khi "duyệt" chuỗi khi các rune được xử lý để tránh nhân đôi nỗ lực giải mã UTF-8.


Khi nào bạn sẽ không thấy rune là một nhân vật? Thông số Go xác định một rune là một mật mã Unicode: golang.org/ref/spec#Rune_literals .
Thomas Kappler

Ngoài ra, để tránh nhân đôi nỗ lực giải mã, tôi chỉ cần thực hiện một [] rune (str), làm việc trên đó, sau đó chuyển đổi trở lại chuỗi khi tôi hoàn thành. Tôi nghĩ rằng điều đó dễ hơn là theo dõi các điểm mã khi đi qua một chuỗi.
Thomas Kappler

4
@ThomasKappler: Khi nào? Chà, khi rune không phải là một nhân vật, mà nó thường không phải là một nhân vật. Chỉ một số rune bằng với các ký tự, không phải tất cả chúng. Giả sử "rune == character" chỉ hợp lệ cho một tập hợp con các ký tự Unicode. Ví dụ: en.wikipedia.org/wiki/ Từ
zzzz

@ThomasKappler: nhưng nếu bạn nhìn vào nó theo cách đó, sau đó ví dụ như Java String's .length()phương pháp không trả lại số ký tự hoặc. Cũng như thế Cocoa của NSString's -lengthphương pháp. Những người chỉ cần trả về số lượng thực thể UTF-16. Nhưng số lượng mật mã thực sự hiếm khi được sử dụng, vì phải mất thời gian tuyến tính để đếm nó.
newacct

5

Nếu bạn cần đưa các cụm grapheme vào tài khoản, hãy sử dụng mô đun regrec hoặc unicode. Việc đếm số lượng điểm mã (runes) hoặc byte cũng cần thiết cho validaiton vì độ dài của cụm grapheme là không giới hạn. Nếu bạn muốn loại bỏ các chuỗi cực dài, hãy kiểm tra xem các chuỗi có phù hợp với định dạng văn bản an toàn luồng không .

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

Cảm ơn vì điều đó. Tôi đã thử mã của bạn và nó không hoạt động đối với một vài biểu đồ biểu tượng cảm xúc như sau:. Bất kỳ suy nghĩ về làm thế nào để đếm chính xác?
Bjorn Roche

Các regrec biên dịch nên được trích xuất như varbên ngoài các chức năng.
heo

5

Có một số cách để có được độ dài chuỗi:

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

Tôi nên chỉ ra rằng cho đến nay, không có câu trả lời nào được cung cấp cho bạn số lượng ký tự như bạn mong đợi, đặc biệt là khi bạn giao tiếp với biểu tượng cảm xúc (nhưng cũng có một số ngôn ngữ như tiếng Thái, tiếng Hàn hoặc tiếng Ả Rập). Đề xuất của VonC sẽ xuất ra như sau:

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

Đó là bởi vì các phương thức này chỉ tính các điểm mã Unicode. Có nhiều ký tự có thể bao gồm nhiều điểm mã.

Tương tự cho việc sử dụng gói Chuẩn hóa :

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

Chuẩn hóa không thực sự giống như đếm các ký tự và nhiều ký tự không thể được chuẩn hóa thành tương đương một mã.

Câu trả lời của masakielastic đến gần nhưng chỉ xử lý các công cụ sửa đổi (cờ cầu vồng chứa một công cụ sửa đổi do đó không được tính là điểm mã riêng của nó):

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

Cách chính xác để phân chia chuỗi Unicode thành các ký tự (do người dùng cảm nhận), tức là cụm grapheme, được xác định trong Phụ lục tiêu chuẩn Unicode # 29 . Các quy tắc có thể được tìm thấy trong Mục 3.1.1 . Các github.com/rivo/uniseg cụ gói các quy tắc, do đó bạn có thể xác định chính xác số ký tự trong một chuỗi:

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

0

Tôi đã cố gắng thực hiện để chuẩn hóa nhanh hơn một chút:

    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
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.