Як отримати кількість символів у рядку?


145

Як я можу отримати кількість символів рядка в Go?

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


2
Це повертається 5 . Можливо, це не відбувається, коли кодування файлу - UTF-8.
Моше Рева

7
Так, це в цьому випадку, але я хочу зробити це загальним для інших символів UTF-8, таких як арабська, що не перекладається на 1 байт.
Аммар

Відповіді:


177

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

повертає кількість рун у p

що, як проілюстровано у цьому сценарії : довжина "Світу" може бути 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.


А з CL 108985 (травень 2018 року, для Go 1.11) len([]rune(string))тепер оптимізовано. (Виправлення випуску 24923 )

Компілятор визначає len([]rune(string))шаблон автоматично і замінює його на r: = range s call.

Додає нову функцію виконання для підрахунку рун у рядку. Змінює компілятор для виявлення шаблону 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\u0301" в NFD). Разом ці дві руни - один символ .

Визначення символу може змінюватися залежно від програми.
Для нормалізації ми визначимо це як:

  • послідовність рун, яка починається зі стартера,
  • руна, яка не змінює і не поєднує назад з будь-якою іншою руною,
  • з наступною можливою порожньою послідовністю нестартерів, тобто рун, які це роблять (як правило, наголоси).

Алгоритм нормалізації обробляє один символ одночасно.

Використовуючи цей пакет та його 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)
}

Тут для цього використовується форма нормалізації Unicode NFKD "Розбір сумісності"


Oliver «s відповідь вказує на UNICODE TEXT СЕГМЕНТАЦІЇ як єдиний спосіб надійно визначити межі по замовчуванням між деякими значними елементами тексту: призначені для користувача сприймаються символів, слів і фраз.

Для цього вам потрібна зовнішня бібліотека на зразок rivo / uniseg , яка робить сегментацію тексту Unicode .

Це насправді буде рахувати " кластер графеми ", де кілька точок коду можуть бути об'єднані в один сприйнятий користувачем символ.

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

Дві графеми, хоча є три руни (кодові точки Unicode).

Ви можете побачити інші приклади в " Як маніпулювати рядками в GO, щоб повернути їх назад? "

👩🏾‍🦰 одне - це одна графема, але, від унікоду до перетворювача кодів , 4 руни:


4
Ви можете побачити його в дії в цьому рядку функції реверсії в stackoverflow.com/a/1758098/6309
VonC

5
Це говорить лише про кількість рун, а не про кількість гліфів. Багато гліфів виготовлені з декількох рун.
Стівен Вайнберг

5
Насправді ви можете зробити len () над рунами, просто ввівши кастинг ... len ([] rune ("世界")) надрукує 2. На літах у Go 1.3, не знаю, скільки часу це було.
Заморожений

3
@VonC: Насправді, персонаж (розмовний термін для гліфа) може - іноді - охоплювати кілька рун, тому ця відповідь полягає у використанні точного технічного терміна WRONG. Що вам потрібно - це графема / 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 ідеально підходить для вас. В іншому випадку, мабуть, слід зазначити, що існує мало ситуацій, коли кількість рун у рядку Unicode є цікавим значенням. І навіть у таких ситуаціях краще, якщо можливо, зробити висновок під час "проходження" рядка під час обробки рун, щоб уникнути подвоєння зусиль на декодування UTF-8.


Коли б ти не бачив руну як персонажа? Специфікація Go визначає руну як кодову точку Unicode: golang.org/ref/spec#Rune_literals .
Томас Капплер

Крім того, щоб уникнути подвоєння зусиль на декодування, я просто роблю [] руну (str), працюю над цим, а потім перетворюю назад у рядок, коли закінчу. Я думаю, що це простіше, ніж відслідковувати кодові точки під час проходження рядка.
Томас Капплер

4
@ThomasKappler: Коли? Добре, коли руна - це не характер, якого, як правило, немає. Тільки деякі руни рівні персонажам, не всі вони. Якщо припустити, що "rune == znak" дійсний лише для підмножини символів Unicode. Приклад: en.wikipedia.org/wiki/…
zzzz

@ThomasKappler: але якщо ви подивіться на це таким чином, то , наприклад , в Java String«s .length()метод не повертає кількість символів , або. Ні робить Какао NSString«s -lengthметод. Вони просто повертають кількість об'єктів UTF-16. Але справжня кількість кодових точок використовується рідко, оскільки для її підрахунку потрібен лінійний час.
newacct

5

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

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

Дякую за це Я спробував ваш код, і він не працює для кількох графічних графічних емоцій, таких як: these. Будь-які думки про те, як їх точно порахувати?
Бйорн Рош

Скомпільований регулярний вираз повинен бути вилучений varпоза функціями.
долмен

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".

Це тому, що ці методи підраховують лише точки коду Unicode. Є багато символів, які можуть складатися з декількох точок коду.

Те ж саме для використання пакету нормалізації :

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

Нормалізація насправді не збігається з підрахунком символів, і багато символів не можуть бути нормалізовані в еквівалент однокодової точки.

Відповідь masakielastic наближається, але лише обробляє модифікатори (прапор веселки містить модифікатор, який, таким чином, не зараховується як власний код):

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

Правильний спосіб розділити рядки Unicode на (сприйняті користувачем) символи, тобто кластери графеми, визначений у стандартному додатку Unicode №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 та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.