문자열에서 문자 수를 얻는 방법은 무엇입니까?


145

Go에서 문자열의 문자 수를 어떻게 얻을 수 있습니까?

예를 들어 문자열 "hello"이 있으면 메서드가 반환해야합니다 5. 나는 보았다 len(str)반환에게 바이트 수 와하지 그래서 문자 수 len("£")£이 UTF-8 2 바이트로 인코딩되어 있기 때문에 수익률이 대신 일을.


2
5를 반환 합니다. 파일 인코딩이 UTF-8이 아닐 수도 있습니다.
Moshe Revah

7
예,이 경우에는 해당하지만 아랍어와 같은 다른 UTF-8 문자 (일반적으로 1 바이트로 변환되지 않음)의 경우 일반으로 만들고 싶습니다.
Ammar

답변:


177

RuneCountInStringutf8 패키지에서 시도해 볼 수 있습니다 .

p의 룬 수를 반환합니다.

이 스크립트 에서 볼 수 있듯이 "월드"의 길이는 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 년 5 월, Go 1.11)로 len([]rune(string))최적화되었습니다. (Fixes 이슈 24923) )

컴파일러는 len([]rune(string))패턴을 자동으로 감지 하여 r : = range 호출로 대체합니다.

문자열에서 룬을 계산하기 위해 새로운 런타임 함수를 추가합니다. 패턴을 감지하도록 컴파일러를 수정하고 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 "호환성 분해"를 사용합니다.


Oliver답변UNICODE TEXT SEGMENTATION 을 특정 중요한 텍스트 요소 (사용자 인식 문자, 단어 및 문장) 사이의 기본 경계를 안정적으로 결정하는 유일한 방법으로 지적합니다.

이를 위해서는 rivo / uniseg 와 같은 외부 라이브러리가 필요합니다.이 라이브러리 는 Unicode Text Segmentation 입니다.

실제로는 " grapheme cluster "로 계산됩니다 . 여기서 여러 코드 포인트가 하나의 사용자 인식 문자로 결합 될 수 있습니다.

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

3 개의 룬 (유니 코드 코드 포인트)이 있지만 2 개의 그래 핀입니다.

당신은 "다른 예를 볼 수 있습니다 을 반대로 GO 문자열을 조작하는 방법을? "

👩🏾‍🦰은 하나의 그래프이지만 유니 코드에서 코드 포인트 변환기 까지 4 개의 룬입니다.


4
이 문자열 복귀 기능에서 stackoverflow.com/a/1758098/6309
VonC

5
글리프 수가 아닌 룬 수만 알려줍니다. 많은 글리프는 여러 룬으로 구성됩니다.
Stephen Weinberg

5
실제로 유형 캐스팅만으로 룬에 대해 len ()을 수행 할 수 있습니다 ... len ([] rune ( "世界"))은 2를 인쇄합니다. Go 1.3의 도약에서는 얼마나 오래 걸렸습니까?
Phrozen

3
@VonC : 사실, 문자 (Glyph의 구어체 용어)는 때때로 여러 룬에 걸쳐있을 수 있으므로이 대답은 정확한 기술 용어 인 WRONG을 사용하는 것입니다. 필요한 것은 룬 수가 아닌 Grapheme / GraphemeCluster 수입니다. 예를 들어, 'e'와 '◌́'(급성 "\ u0301")은 결합하여 'é'(NFD의 "e \ u0301")를 형성 할 수 있습니다. 그러나 인간은 (올바르게) & eacute를 고려할 것이다. 한 문자로 .. 분명히 그것은 텔루구 어에서 차이를 만듭니다. 그러나 사용하는 키보드 / 로케일에 따라 프랑스어 일 수도 있습니다. blog.golang.org/normalization
Stefan Steiger

1
@JustinJohnson 합의. 나는 이전에 upvoted했던 Oliver를 더 잘 참조하기 위해 답을 편집했습니다.
VonC

43

다음과 같이 문자열을 [] 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


5

"캐릭터"가 무엇인지에 대한 정의에 많이 의존합니다. 작업에 "rune is a character"가 정상이면 (일반적으로 그렇지 않은 경우) VonC의 답변이 귀하에게 적합합니다. 그렇지 않으면 유니 코드 문자열의 룬 수가 흥미로운 값인 상황이 거의 없다는 점에 유의해야합니다. 이러한 상황에서도 UTF-8 디코드 노력이 배가되는 것을 피하기 위해 룬이 처리 될 때 문자열을 "순회"하는 동안 카운트를 유추하는 것이 좋습니다.


당신은 할 때 하지 문자로 룬을 볼? Go 스펙은 룬을 유니 코드 코드 포인트 ( golang.org/ref/spec#Rune_literals) 로 정의합니다 .
Thomas Kappler

또한 디코딩 노력을 두 배로 늘리지 않으려면 [] rune (str)을 수행하고 작업 한 다음 완료되면 문자열로 다시 변환하십시오. 문자열을 탐색 할 때 코드 포인트를 추적하는 것이 더 쉽다고 생각합니다.
Thomas Kappler

4
@ThomasKappler : 언제? 글쎄, 룬 문자가 아닌 경우 일반적으로 그렇지 않습니다. 일부 룬만 문자와 같지만 모든 룬은 아닙니다. "rune == character"라고 가정하면 유니 코드 문자의 하위 집합에만 유효합니다. 예 : en.wikipedia.org/wiki/…
zzzz

@ThomasKappler : 당신이 그것을 그런 식으로 보면하지만, 다음 예를 들어 자바 String.length()방법 중 문자 수를 반환하지 않습니다. 둘 다 코코아의하지 않습니다 NSString-length방법. 그것들은 단순히 UTF-16 엔티티의 수를 반환합니다. 그러나 실제 코드 포인트 수는 계산에 선형 시간이 걸리기 때문에 거의 사용되지 않습니다.
newacct

5

grapheme 클러스터를 고려해야 할 경우 regexp 또는 unicode 모듈을 사용하십시오. 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) 
}

고마워 나는 당신의 코드를 시도했지만 다음과 같은 몇 가지 이모티콘 그래프에는 작동하지 않습니다 : 🖖🏿🇸🇴. 정확하게 계산하는 방법에 대한 생각이 있습니까?
Bjorn Roche

컴파일 된 정규 표현식은 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".

이러한 메서드는 유니 코드 코드 포인트 만 계산하기 때문입니다. 여러 코드 포인트로 구성 될 수있는 많은 문자가 있습니다.

정규화 패키지 를 사용하는 경우와 동일 합니다 .

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

정규화는 실제로 문자를 세는 것과 같지 않으며 많은 문자를 1 코드 포인트로 정규화 할 수 없습니다.

masakielastic의 대답 은 가깝지만 수정자를 처리합니다 (무지개 플래그에는 수정자가 포함되어 자체 코드 포인트로 계산되지 않음).

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

Unicode 문자열을 (사용자 인식) 문자, 즉 grapheme 클러스터로 나누는 올바른 방법은 Unicode Standard Annex # 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
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.