Как получить число символов в строке?

Как я могу получить количество символов строки в Go?

Например, если у меня есть строка "hello", метод должен возвращать 5. Я видел, что len(str) возвращает количество байтов и не количество символов, поэтому len("£") возвращает 2 вместо 1, потому что E кодируется двумя байтами в UTF-8.

Ответ 1

Вы можете попробовать RuneCountInString из пакета utf8.

возвращает количество рун в p

что, как показано в этот script: длина "мира" может составлять 6 (при написании на китайском языке: "世界" ), но его количество руны равно 2:

package main

import "fmt"
import "unicode/utf8"

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

Phrozen добавляет в комментариях:

На самом деле вы можете сделать len() по рунам, просто набрав тип. len([]rune("世界")) напечатает 2. На литах в Go 1.3.


Stefan Steiger указывает на сообщение в блоге Нормализация текста в Go "

Что такое символ?

Как упоминалось в сообщении строковых сообщений, символы могут охватывать несколько рун.
Например, "e" и "◌◌" (острый "\ u0301" ) могут объединяться для формирования "é" ( "e\u0301" в NFD). Вместе эти две руны - это один символ.

Определение символа может варьироваться в зависимости от приложения.
Для normalization мы определим его как:

  • последовательность рун, начинающихся со стартера,
  • руна, которая не изменяет или не объединяется с другой руной,
  • за которым следует, возможно, пустая последовательность non-starters, то есть руны, которые выполняют (обычно акценты).

Алгоритм нормализации обрабатывает один символ во время.

Используя этот пакет и его Iter type, фактическое количество символов будет:

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

Здесь используется форма Unicode Normalization NFKD "Разложение совместимости"

Ответ 2

Существует способ получить количество рун без каких-либо пакетов путем преобразования строки в [] rune как 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

Ответ 3

Зависит от вашего определения того, что такое "персонаж". Если "руна равна персонажу" подходит для вашей задачи (обычно это не так), то ответ VonC идеально подходит для вас. В противном случае, вероятно, следует отметить, что существует несколько ситуаций, когда количество рун в строке Юникода представляет интересное значение. И даже в таких ситуациях лучше, если это возможно, заключить счет во время "пересечения" строки, когда руны обрабатываются, чтобы избежать удвоения усилий декодирования UTF-8.

Ответ 4

Если вам нужно учитывать кластеры grapheme, используйте модуль regexp или unicode. Подсчет количества кодовых точек (рун) или байтов также необходим для validaiton, так как длина кластера grapheme не ограничена. Если вы хотите устранить чрезвычайно длинные последовательности, проверьте, соответствуют ли последовательности потокобезопасный текстовый формат.

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